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?
Odpowiedzi:
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
HashMap
metody, 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,HashMap
gdy 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ć
TreeMap
lubLinkedHashMap
być przekazywane, albo jeszcze lepiejSortedMap
, 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ćSortedMap
i 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
Dog
i zCat
których wywodziszAnimal
, w celu jak najlepszego wykorzystania dziedziczenia, ogólnie powinieneś unikać metod specyficznych dlaDog
lubCat
. Raczej wszystkie metody wDog
lubCat
powinny zastąpić metody w,Animal
a na dłuższą metę zaoszczędzi ci kłopotów.źródło
SortedMap
niżTreeMap
.SortedMap
jest jednym z kilku wdrożeń dotyczących zamówień. To poza tym.TreeMap
zamawia również przedmioty zgodnie z implementacją kluczaComparable
lubComparator
interfejsem.LinkedHashMap
nie implementujeSortedMap
. Jedynymi podklasamiSortedMap
sąConcurrentSkipListMap
iTreeMap
.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.
źródło
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 wymaga
HashMap
. Jeśli zwrócisz aMap
, 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.źródło
Ograniczenie zmiennej do interfejsu gwarantuje, że żadne użycie tej zmiennej nie będzie wykorzystywać
HashMap
okreś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. .
źródło