Przeglądając Framework kolekcji Java, zauważyłem, że sporo interfejsów ma komentarz (optional operation)
. Te metody pozwalają na implementację klas, UnsupportedOperationException
jeśli nie chcą po prostu implementować tej metody.
Przykładem tego jest addAll
metoda w Set Interface
.
Teraz, jak stwierdzono w tej serii pytań, interfejsy są definiującą umową dotyczącą tego, czego może oczekiwać użycie.
Interfejsy są ważne, ponieważ oddzielają to, co robi klasa od tego, jak to robi. Umowa określająca, czego może oczekiwać klient, pozostawia deweloperowi swobodę wdrażania go w dowolny sposób, pod warunkiem przestrzegania umowy.
i
Interfejs to opis działań, które może wykonywać obiekt ... na przykład po naciśnięciu przełącznika światła światło się zapala, nie obchodzi cię, jak to działa. W programowaniu obiektowym interfejs to opis wszystkich funkcji, które musi posiadać obiekt, aby mógł być „X”.
i
Myślę, że podejście oparte na interfejsie jest znacznie ładniejsze. Następnie możesz ładnie wyśmiewać swoje zależności i wszystko jest zasadniczo mniej ściśle powiązane.
Interfejs + rozszerzenie (mixin) vs klasa podstawowa
Biorąc pod uwagę, że celem interfejsów jest zdefiniowanie kontraktu i luźne powiązanie twoich zależności, czy posiadanie niektórych metod nie jest UnsupportedOperationException
rodzajem porażki? Oznacza to, że nie mogę już przejść Set
i po prostu użyć addAll
. Muszę raczej wiedzieć, jaką implementację Set
przeszedłem, więc mogę wiedzieć, czy mogę użyć, addAll
czy nie. To wydaje mi się dość bezwartościowe.
Jaki jest więc sens UnsupportedOperationException
? Czy to po prostu rekompensowanie starszego kodu i muszą wyczyścić swoje interfejsy? Czy może ma sens bardziej sensowny, że tęsknię?
źródło
addAll
wHashSet
. Odsyła do domyślnej implementacji, wAbstractCollection
której z pewnością nie rzucaUnsupportedOperationException
.src.zip
nim połączone , działa świetnie. Pomaga dokładnie wiedzieć, jaki kod JRE czasami uruchamia, i nie odsuwać się do JavaDoc, który może być nieco gadatliwy.Odpowiedzi:
Spójrz na następujące interfejsy:
Wszystkie te interfejsy deklarują metody mutacji jako opcjonalne. Jest to domyślnie dokumentujące fakt, że klasa Kolekcje może zwrócić implementacje niezmiennych interfejsów: to znaczy, że te opcjonalne operacje mutacji na pewno się nie powiodą. Jednak zgodnie z umową w JavaDoc wszystkie implementacje tych interfejsów muszą umożliwiać operacje odczytu. Obejmuje to „normalne” implementacje, takie jak
HashSet
iLinkedList
niezmienne opakowania wCollections
.Porównaj z interfejsami kolejki:
Interfejsy te nie określają żadnych operacji opcjonalnych: kolejka z definicji jest zaprojektowana do oferowania i odpytywania elementów w sposób FIFO. Niezmienna kolejka jest tak samo przydatna jak samochód bez kół.
Jednym z powszechnych pomysłów, który pojawia się wielokrotnie, jest hierarchia dziedziczenia obejmująca zarówno obiekty zmienne, jak i niezmienne. Wszystkie te mają jednak wady. Złożoność moczy wody, nie rozwiązując problemu.
Hipotetyczne
Set
mogą mieć operacje odczytu, a podinterfaceMutableSet
może mieć operacje zapisu. Liskov mówi nam, żeMutableSet
można następnie przekazać wszystko, co potrzebujeSet
. Na początku brzmi to dobrze, ale rozważ metodę, która oczekuje, że podstawowy zestaw nie zostanie zmodyfikowany podczas odczytu: możliwe byłoby, aby dwa wątki użyły tego samego zestawu i naruszyłyby niezmienność zestawu, który się nie zmienia. Może to powodować problem, np. Jeśli metoda odczytuje element ze zbioru dwa razy i jest tam za pierwszym razem, ale nie za drugim razem.Set
może nie mieć bezpośrednich implementacji, zamiast tego maMutableSet
iImmutableSet
jako podinterfejsy, które są następnie wykorzystywane do implementacji klas. Ma to ten sam problem, co powyżej: w pewnym momencie w hierarchii interfejs ma niezmienniki powodujące konflikt. Jeden mówi „ten zestaw musi być modyfikowalny”, a drugi mówi „ten zestaw nie może się zmienić”.Mogą istnieć dwie całkowicie odrębne hierarchie dla zmiennych i niezmiennych struktur danych. Dodaje to mnóstwo dodatkowej złożoności, co w rezultacie daje bardzo niewielki zysk. Ma to również swoistą słabość metod, które nie dbają o zmienność (np. Chcę tylko iterować listę), musi teraz obsługiwać dwa oddzielne interfejsy. Ponieważ Java jest typowana statycznie, oznacza to dodatkowe metody obsługi obu hierarchii interfejsów.
Możemy mieć jeden interfejs i zezwalać implementacjom na zgłaszanie wyjątków, jeśli metoda nie ma do niego zastosowania. Jest to droga, którą wybrała Java, i ma to największy sens. Liczba interfejsów jest ograniczona do minimum i nie ma niezmienników zmienności, ponieważ udokumentowany interfejs nie daje żadnej gwarancji na zmienność w żaden sposób . Jeśli wymagany jest niezmienność niezmienności, użyj opakowań w
Collections
. Jeśli metoda nie musi zmieniać kolekcji, po prostu jej nie zmieniaj. Wadą jest to, że metoda nie może zagwarantować, że kolekcja nie zmieni się w innym wątku, jeśli zostanie podana kolekcja z zewnątrz, ale to i tak dotyczy metody wywołującej (lub jej metody wywoływania).Literatura pokrewna: Dlaczego Java 8 nie zawiera niezmiennych kolekcji?
źródło
MutableCollection
?Zasadniczo jest to YAGNI. Wszystkie konkretne kolekcje w standardowej bibliotece można modyfikować, implementując lub dziedzicząc operacje opcjonalne. Nie dbają o niezmienne kolekcje ogólnego przeznaczenia, a także ogromna większość programistów Java. Nie zamierzają tworzyć całej hierarchii interfejsów tylko dla niezmiennych kolekcji, a następnie nie będą zawierać żadnych implementacji.
Z drugiej strony istnieje kilka wartości specjalnego przeznaczenia lub „wirtualnych” kolekcji, które mogą być bardzo przydatne jako niezmienne, takie jak pusty zestaw i nCopies . Ponadto istnieją niezmienne kolekcje innych firm (takie jak Scala), które mogą chcieć wywoływać istniejący kod Java, więc pozostawiono możliwość otwierania niezmiennych kolekcji w najmniej zakłócający sposób.
źródło