Podczas mojej pierwszej implementacji rozszerzającej środowisko kolekcji Java byłem zaskoczony, widząc, że interfejs kolekcji zawiera metody zadeklarowane jako opcjonalne. Oczekuje się, że implementator zgłosi UnsupportedOperationExceptions, jeśli nie jest obsługiwany. Od razu uderzyło mnie to jako kiepski wybór interfejsu API.
Po przeczytaniu dużej części doskonałej książki Joshuy Blocha „Skuteczna Java”, a później nauczeniu się, że może być odpowiedzialny za te decyzje, nie wydawało się, że żeluje to z zasadami zawartymi w książce. Pomyślałem, że zadeklarowanie dwóch interfejsów: Collection i MutableCollection, które rozszerzy Collection przy pomocy „opcjonalnych” metod, doprowadziłoby do znacznie łatwiejszego do utrzymania kodu klienta.
Jest to doskonałe podsumowanie kwestii tutaj .
Czy był dobry powód, dla którego wybrano opcjonalne metody zamiast implementacji dwóch interfejsów?
źródło
Odpowiedzi:
FAQ dostarcza odpowiedzi. Krótko mówiąc, widzieli potencjalną eksplozję kombinatoryczną potrzebnych interfejsów z modyfikowalnym, niemodyfikowalnym widokiem, tylko do usuwania, tylko do dodawania, o stałej długości, niezmienny (do wątkowania) i tak dalej dla każdego możliwego zestawu zaimplementowanych metod opcji.
źródło
const
słowo kluczowe takie jak C ++.vector<int>, const vector<int>, vector<const int>, const vector<const int>
. Jak dotąd dobrze, ale próbujesz zaimplementować grafy i chcesz, aby struktura wykresu była stała, ale atrybuty węzła można modyfikować itp.can
metody, która sprawdziłaby, czy operacja jest możliwa? Dzięki temu interfejs byłby prosty i szybki.Wydaje mi się, że
Interface Segregation Principle
wtedy nie było tak dobrze zbadane, jak teraz; ten sposób robienia rzeczy (tj. twój interfejs zawiera wszystkie możliwe operacje i masz metody „zdegenerowane”, które generują wyjątki dla tych, których nie potrzebujesz) był popularny zanim SOLID i ISP stały się de facto standardem dla kodu jakości.źródło
Count
kolekcję bez martwienia się o to, jakie typy elementów zawiera), ale w ramach opartych na usuwaniu czcionek, takich jak Java, nie jest to taki problem.Chociaż niektórzy ludzie nie znoszą „metod opcjonalnych”, w wielu przypadkach mogą oferować lepszą semantykę niż wysoce rozdzielone interfejsy. Pozwalają między innymi na to, że obiekt może zyskać zdolności lub cechy w trakcie swojego życia, lub że obiekt (zwłaszcza obiekt otoki) może nie wiedzieć, kiedy jest skonstruowany, jakie dokładnie umiejętności powinien zgłosić.
Chociaż raczej nie będę nazywał klas języka Java wzorami dobrego projektowania, sugerowałbym, że dobry framework kolekcji powinien u podstaw zawierać dużą liczbę opcjonalnych metod wraz ze sposobami zapytania kolekcji o jej cechy i możliwości . Taki projekt pozwoli na użycie pojedynczej klasy opakowania z dużą różnorodnością kolekcji bez przypadkowego zaciemnienia umiejętności, jakie może mieć kolekcja podstawowa. Jeśli metody nie były opcjonalne, konieczne byłoby posiadanie innej klasy opakowania dla każdej kombinacji funkcji obsługiwanych przez kolekcje, w przeciwnym razie niektóre opakowania nie nadawałyby się do użytku w niektórych sytuacjach.
Na przykład, jeśli kolekcja obsługuje pisanie elementu według indeksu lub dodawanie elementów na końcu, ale nie obsługuje wstawiania elementów na środku, to kod chcący obudować go w opakowaniu, który rejestrowałby wszystkie wykonane na nim akcje, potrzebowałby wersji opakowania, które zapewniało dokładną kombinację obsługiwanych zdolności, lub jeśli żadna nie była dostępna, musiałaby użyć opakowania, które obsługuje dopisywanie lub zapisywanie według indeksu, ale nie oba. Jeśli jednak ujednolicony interfejs kolekcji zapewnia wszystkie trzy metody jako „opcjonalne”, ale następnie obejmuje metody wskazujące, która z opcjonalnych metod będzie użyteczna, wówczas pojedyncza klasa opakowania może obsługiwać kolekcje, które implementują dowolną kombinację funkcji. Na pytanie, jakie funkcje obsługuje, opakowanie może po prostu zgłosić wszystko, co obsługuje kolekcja enkapsulowana.
Zauważ, że istnienie „opcjonalnych zdolności” może w niektórych przypadkach pozwolić zbiorczym kolekcjom na wdrożenie niektórych funkcji w sposób, który byłby znacznie bardziej wydajny niż byłoby to możliwe, gdyby umiejętności były zdefiniowane przez istnienie implementacji. Załóżmy na przykład, że
concatenate
użyto metody do utworzenia zbioru złożonego z dwóch innych, z których pierwszym był ArrayList z 1 000 000 elementów, a ostatnim z nich była kolekcja dwudziestoelementowa, którą można było iterować tylko od początku. Jeśli poproszono by o zbiór złożony o 1 000 013 element (indeks 1 000 012), mógłby zapytać ArrayList, ile elementów zawiera (tj. 1 000 000), odjąć to od żądanego indeksu (dając 12), odczytać i pominąć dwanaście elementów od drugiego kolekcja, a następnie zwróć następny element.W takiej sytuacji, mimo że kolekcja złożona nie miałaby natychmiastowego sposobu na zwrócenie pozycji według indeksu, zapytanie kolekcji zbiorczej o 1 000 013 pozycji byłoby nadal znacznie szybsze niż odczytanie z niej 1 000 013 pozycji z osobna i zignorowanie wszystkich oprócz ostatniej jeden.
źródło
AsXXX
metodę w interfejsie podstawowym, która zwróci obiekt, na który jest wywoływany, jeśli implementuje ten interfejs, zwróci obiekt opakowania, który obsługuje ten interfejs, jeśli to możliwe, lub wyrzuci wyjątek, jeśli nie. Na przykładImmutableCollection
interfejs może wymagać umowy ...Przypisałbym to oryginalnym programistom, którzy nie wiedzieli wtedy lepiej. Przeszliśmy długą drogę w projektowaniu OO od 1998 roku, kiedy po raz pierwszy wydano Java 2 i kolekcje. To, co wydaje się oczywistym złym projektem, nie było już tak oczywiste we wczesnych latach OOP.
Ale mogło to zostać zrobione, aby zapobiec dodatkowemu rzucaniu. Gdyby to był drugi interfejs, musiałbyś rzutować instancje kolekcji, aby wywoływać te opcjonalne metody, co również jest trochę brzydkie. W tej chwili możesz natychmiast złapać UnsupportedOperationException i naprawić swój kod. Ale gdyby istniały dwa interfejsy, trzeba by użyć instancji i rzutowania w dowolnym miejscu. Być może uznali to za ważny kompromis. Również we wczesnej wersji Java 2-dniowe wystąpienie instof było bardzo niezadowolone z powodu jego niskiej wydajności, mogli próbować zapobiec jego nadmiernemu użyciu.
Oczywiście to wszystko dzikie spekulacje. Wątpię, czy moglibyśmy odpowiedzieć na to pytanie na pewno, chyba że zadzwoni jedna z oryginalnych kolekcji architektów.
źródło
Collection
a nie aMutableCollection
, jest to wyraźny znak, że nie jest przeznaczona do modyfikacji. Nie wiem, dlaczego ktoś musiałby je rzucić. Posiadanie osobnych interfejsów oznacza, że będziesz otrzymywać tego rodzaju błędy w czasie kompilacji zamiast uzyskiwania wyjątku w czasie wykonywania. Im wcześniej pojawi się błąd, tym lepiej.const
obiekt, natychmiast mówiąc użytkownikowi, że obiektu nie można modyfikować.