Po co definiować obiekt Java za pomocą interfejsu (np. Mapa) zamiast implementacji (HashMap)

17

W większości kodu Java widzę, że ludzie deklarują takie obiekty Java:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

zamiast:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

Dlaczego preferuje się definiowanie obiektu Java za pomocą interfejsu, a nie implementacji, która będzie faktycznie używana?

Suman
źródło

Odpowiedzi:

26

Powodem jest to, że implementacja tych interfejsów zwykle nie jest istotna podczas ich obsługi, dlatego jeśli zobowiązujesz osobę wywołującą do przekazania HashMapmetody, to zasadniczo zobowiązujesz się, której implementacji użyć. Zasadniczo więc powinieneś obsługiwać interfejs, a nie rzeczywistą implementację, i unikać bólu i cierpienia, które mogą skutkować koniecznością zmiany wszystkich sygnatur metod, HashMapgdy zdecydujesz, że musisz go użyć LinkedHashMap.

Należy powiedzieć, że istnieją wyjątki od tego, kiedy wdrożenie jest istotne. Jeśli potrzebujesz mapy, gdy zlecenie jest ważne, to można wymagać TreeMaplub LinkedHashMapbyć przekazywane, albo jeszcze lepiej SortedMap, które nie określa konkretnej implementacji. To zobowiązuje osobę dzwoniącą do koniecznego przejścia określonego typu implementacji mapy i zdecydowanie wskazuje, że kolejność jest ważna. To powiedziawszy, czy możesz przesłonić SortedMapi przekazać nieposortowane? Tak, oczywiście, jednak oczekuj, że w rezultacie wydarzy się coś złego.

Jednak najlepsza praktyka wciąż wskazuje, że jeśli nie jest to ważne, nie należy używać określonych implementacji. Tak jest ogólnie. Jeśli masz do czynienia Dogi z Catktórych wywodzisz Animal, w celu jak najlepszego wykorzystania dziedziczenia, ogólnie powinieneś unikać metod specyficznych dla Doglub Cat. Raczej wszystkie metody w Doglub Catpowinny zastąpić metody w, Animala na dłuższą metę zaoszczędzi ci kłopotów.

Neil
źródło
Gdy potrzebujesz posortowanej mapy, typ parametru powinien być inny SortedMapniż TreeMap.
Głowonóg
@Arian SortedMapjest jednym z kilku wdrożeń dotyczących zamówień. To poza tym. TreeMapzamawia również przedmioty zgodnie z implementacją klucza Comparablelub Comparatorinterfejsem.
Neil
Nie, SortedMap nie jest implementacją, właśnie o to chodzi. Jest to interfejs map sortowanych według klucza.
Głowonóg
1
@Arian Ah Rozumiem, co masz na myśli. To prawda, lepiej SortedMap, ponieważ nie wymusza to implementacji. Dokonam odpowiednich poprawek.
Neil
W rzeczywistości a LinkedHashMapnie implementuje SortedMap. Jedynymi podklasami SortedMapConcurrentSkipListMapi TreeMap.
bcorso
10

Słowami laika:

Z tego samego powodu producenci urządzeń elektrycznych budowali swoje produkty za pomocą wtyczek elektrycznych zamiast po prostu odrywanych kabli, a domy mają gniazdka ścienne zamiast odrywania kabli wystających ze ściany.

Używając zamiast tego standardowych wtyczek, umożliwiają podłączenie tych samych urządzeń do dowolnej kompatybilnej wtyczki w domu.

Z punktu widzenia gniazdka ściennego nie ma znaczenia, czy podłączysz telewizor, czy stereo.

To sprawia, że ​​zarówno urządzenie, jak i gniazdo są bardziej przydatne.

Weźmy na przykład metodę, która akceptuje mapę jako argument.

Metoda będzie działać niezależnie od tego, czy przekażesz do niej HashMap lub LinkedHashMap, pod warunkiem, że będzie to podklasa Map.

To jest zasada podstawienia Liskowa .

W przykładowym kodzie, który podałeś, oznacza to, że możesz później, z jakiegoś powodu, zmienić konkretną implementację Hash i nie będziesz musiał zmieniać reszty kodu.

Problem z oprogramowaniem polega na tym, że ponieważ stosunkowo łatwo jest później zmienić rzeczy bez marnowania cegieł i zaprawy, ludzie uważają, że takie przewidywanie nie jest warte czasu. Ale rzeczywistość pokazała nam, że konserwacja oprogramowania jest bardzo droga.

Tulains Córdova
źródło
4

Jest to zgodne z zasadą segregacji interfejsu („I” w SOLID ). Zapobiega to uzależnieniu kodu, który korzysta z tych obiektów, od metod tych obiektów, których nie potrzebuje, co czyni kod mniej sprzężonym, a zatem łatwiejszym do zmiany.

Na przykład, jeśli później dowiesz się, że naprawdę potrzebujesz LinkedHashMap, możesz bezpiecznie wprowadzić tę zmianę bez wpływu na inny kod.

Jest jednak kompromis, ponieważ sztucznie ograniczasz kod, który może przyjmować twój obiekt jako parametr. Powiedzmy, że jest gdzieś funkcja, która z jakiegoś powodu wymagaHashMap . Jeśli zwrócisz a Map, nie możesz przekazać swojego obiektu do tej funkcji. Musisz zrównoważyć prawdopodobieństwo, że w przyszłości będziesz potrzebować dodatkowej funkcjonalności w bardziej konkretnej klasie, z chęcią ograniczenia sprzężenia i utrzymania możliwie jak najmniejszego interfejsu publicznego.

Karl Bielefeldt
źródło
3

Ograniczenie zmiennej do interfejsu gwarantuje, że żadne użycie tej zmiennej nie będzie wykorzystywać HashMapokreślonej funkcjonalności, która może nie istnieć w interfejsie, więc instancja może zostać później zmieniona bez obaw na inną implementację, o ile nowa instancja również implementuje berło.

Z tego powodu, za każdym razem, gdy chcesz użyć interfejsu obiektów, zawsze dobrą praktyką jest deklarowanie zmiennych jako interfejsu, a nie konkretnej implementacji, dotyczy to wszystkich typów obiektów, których możesz użyć i które mają interfejs. Powodem, dla którego często to widzisz, jest fakt, że wiele osób ma w tym nawyk.

Powiedział, że to nie jest szkodliwe, aby przejść z użyciem interfejsów czasami, a większość z nas niechlujnie nie zawsze przestrzegać tej reguły, bez rzeczywistej szkody. Dobrą praktyką jest trzymanie się, gdy masz wrażenie, że kod może zostać zmieniony i potrzebujesz konserwacji / rozwoju w przyszłości. Nie ma większego znaczenia, gdy hakujesz kod, co do którego nie podejrzewasz, że będzie miało długie życie lub ma duże znaczenie. Również złamanie tej reguły zwykle ma niewielki wpływ na to, że zmiana implementacji na inną może wymagać nieco refaktoryzacji, więc jeśli nie zawsze będziesz jej przestrzegać, nie zrobisz sobie wiele krzywdy, chociaż przestrzeganie tej zasady nie jest również szkodliwe. .

Jimmy Hoffa
źródło