Zastanawiałem się, co sprawia, że Iterator jest wyjątkowy w porównaniu z innymi podobnymi konstrukcjami, i że Gang of Four wymienia go jako wzorzec projektowy.
Iterator opiera się na polimorfizmie (hierarchii zbiorów ze wspólnym interfejsem) i oddzieleniu obaw (iteracja zbiorów powinna być niezależna od struktury danych).
Ale co, jeśli zastąpimy hierarchię zbiorów na przykład hierarchią obiektów matematycznych (liczba całkowita, liczba zmiennoprzecinkowa, liczba zespolona, macierz itp.) I iteratorem klasą reprezentującą niektóre powiązane operacje na tych obiektach, na przykład funkcje mocy. Diagram klas byłby taki sam.
Prawdopodobnie moglibyśmy znaleźć wiele innych podobnych przykładów, takich jak Writer, Painter, Encoder i prawdopodobnie lepsze, które działają w ten sam sposób. Jednak nigdy nie słyszałem, aby którykolwiek z nich nazywał się Wzorem projektowym.
Co sprawia, że Iterator jest wyjątkowy?
Czy to fakt, że jest bardziej skomplikowany, ponieważ wymaga stanu zmiennego do przechowywania bieżącej pozycji w kolekcji? Ale wtedy stan zmienności zwykle nie jest uważany za pożądany.
Aby wyjaśnić mój punkt, pozwól mi podać bardziej szczegółowy przykład.
Oto nasz problem projektowy:
Powiedzmy, że mamy hierarchię klas i operację zdefiniowaną na obiektach tych klas. Interfejs tej operacji jest taki sam dla każdej klasy, ale implementacje mogą być zupełnie inne. Zakłada się również, że sensowne jest wielokrotne zastosowanie operacji na tym samym obiekcie, powiedzmy z różnymi parametrami.
Oto rozsądne rozwiązanie naszego problemu projektowego (praktycznie uogólnienie wzoru iteratora):
W celu rozdzielenia wątpliwości implementacje operacji nie powinny być dodawane jako funkcje do oryginalnej hierarchii klas (obiekty operandów). Ponieważ chcemy zastosować tę operację wiele razy na tym samym operandzie, powinna ona być reprezentowana przez obiekt zawierający odwołanie do operandu, a nie tylko przez funkcję. Dlatego obiekt argumentu powinien zapewniać funkcję, która zwraca obiekt reprezentujący operację. Ten obiekt zapewnia funkcję, która wykonuje faktyczną operację.
Przykład:
Istnieje podstawowa klasa lub interfejs MathObject
(głupia nazwa, wiem, może ktoś ma lepszy pomysł) z klasami pochodnymi MyInteger
i MyMatrix
. Dla każdej MathObject
operacji Power
należy zdefiniować operację, która pozwala obliczyć kwadrat, sześcian i tak dalej. Więc możemy napisać (w Javie):
MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125
źródło
Odpowiedzi:
Większość wzorców z książki GoF ma następujące cechy wspólne:
Problemy rozwiązywane przez te wzorce są tak podstawowe, że wielu programistów rozumie je głównie jako obejścia brakujących funkcji języka programowania , co jest ważnym punktem widzenia IMHO (zauważ, że książka GoF pochodzi z 1995 r., Gdzie Java i C ++ nie oferowały tak wielu funkcje jak dzisiaj).
Wzorzec iteracji dobrze pasuje do tego opisu: rozwiązuje podstawowy problem, który pojawia się bardzo często, niezależnie od jakiejkolwiek konkretnej dziedziny, a jak sam napisałeś, jest to dobry przykład „rozdzielenia problemów”. Jak zapewne wiesz, bezpośrednia obsługa iteratora jest obecnie dostępna w wielu współczesnych językach programowania.
Porównaj to z wybranymi problemami:
Co więcej, nie widzę w twoim przykładzie funkcji mocy nic, co nie mogłoby być interpretowane jako zastosowanie wzorca strategii lub wzorca poleceń, co oznacza, że te podstawowe części są już w książce GoF. Lepsze rozwiązanie może obejmować przeciążenie operatora lub metody rozszerzenia, ale są to rzeczy podlegające funkcjom językowym, a tego właśnie nie może zapewnić „OO” użyty przez „gang”.
źródło
The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features
- Ironią jest to, że programiści rutynowo stosują wzorce projektowania oprogramowania, które mają 20 lat, wciąż wierząc, że piszą najnowocześniejszy kod.IEnumerable
iyield
). Ale w przypadku innych wzorców GoF to, co napisałeś, może być prawdopodobnie prawdą.workarounds for missing programming language features:
blog.plover.com/prog/johnson.htmlThe Gang of Four cytuje definicję wzoru Christophera Alexandra:
Jaki problem rozwiązują iteratory?
Można więc argumentować, że wzorzec iteratora jest z definicji specyficzny dla domeny dla kolekcji. I to jest całkowicie OK. Inne wzorce, takie jak wzorzec interpretera, są specyficzne dla domeny dla języków specyficznych dla domeny, wzorce fabryczne są specyficzne dla domeny dla tworzenia obiektów,… Oczywiście jest to raczej głupie rozumienie „specyficznego dla domeny”. Dopóki jest to powtarzająca się para problem-rozwiązanie, możemy nazwać to wzorcem.
I dobrze, że wzorzec Iterator istnieje. Złe rzeczy dzieją się, jeśli ich nie używasz. Moim ulubionym anty-przykładem jest Perl. Tutaj każda kolekcja (tablica lub skrót) zawiera stan iteratora jako część kolekcji. Dlaczego to takie złe? Z łatwością możemy iterować po skrócie - każda pętla:
Ale co, jeśli nazwiemy funkcję w ciele pętli?
Ta funkcja może teraz robić prawie wszystko, co chce, z wyjątkiem:
Jeśli wywoływana funkcja powinna korzystać z iteratora, zachowanie naszej pętli staje się niezdefiniowane. To jest problemem. A wzorzec iteratora ma rozwiązanie: umieść cały stan iteracji w osobnym obiekcie, który jest tworzony podczas iteracji.
Tak, oczywiście wzorzec iteratora jest powiązany z innymi wzorami. Na przykład, w jaki sposób tworzy się iterator? W Javie mamy ogólny
Iterable<T>
iIterator<T>
interfejs. Konkretne iterowalne jakArrayList<T>
tworzy szczególny rodzaj iteratora, podczas gdyHashSet<T>
może dostarczyć zupełnie innego typu iteratora. To bardzo przypomina mi abstrakcyjny wzór fabryki, gdzieIterable<T>
jest abstrakcyjna fabryka, aIterator
produkt.Iterator polimorficzny można również interpretować jako przykład wzorca strategii. Np. Drzewo może oferować różne rodzaje iteratorów (przedsprzedaż, w kolejności, po zamówieniu,…). Zewnętrznie wszystkie one miałyby wspólny interfejs iteratora i dawałyby elementy w pewnej sekwencji. Kod klienta musi zależeć tylko od interfejsu iteratora, a nie od konkretnego algorytmu przechodzenia przez drzewo.
Wzory nie istnieją w oderwaniu od siebie, niezależnie od siebie. Niektóre wzorce są różnymi rozwiązaniami tego samego problemu, a niektóre wzorce opisują to samo rozwiązanie w różnych kontekstach. Niektóre wzory sugerują inne. Przestrzeń wzorów nie jest również zamykana, gdy przewracasz ostatnią stronę książki Wzory projektowe (patrz także poprzednie pytanie Czy Gang of Four dokładnie zbadał „Przestrzeń wzorów”? ). Wzory opisane w książce Wzory projektowe są bardzo elastyczne i szerokie, otwarte na nieskończoną różnorodność, a na pewno nie jedyne istniejące wzorce.
Pojęcia, które wymieniasz (pisanie, malowanie, kodowanie) nie są wzorami, ponieważ nie opisują połączenia problemu z rozwiązaniem. Zadanie typu „Muszę pisać dane” lub „Muszę kodować dane” nie jest tak naprawdę problemem projektowym i nie obejmuje rozwiązania; „rozwiązanie”, które po prostu składa się z „Wiem, utworzę klasę Writer” jest bez znaczenia. Ale jeśli mamy problem, taki jak „Nie chcę, aby na ekranie malowana była pół renderowana grafika”, może istnieć wzór: „Wiem, użyję grafiki podwójnie buforowanej!”
źródło