Co sprawia, że ​​Iterator jest wzorem projektowym?

9

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 MyIntegeri MyMatrix. Dla każdej MathObjectoperacji Powernależ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
Frank Puffer
źródło
5
„Schemat klas byłby taki sam” - więc co? Wzorzec projektowy nie jest schematem klasowym. Jest to abstrakcja wysokiego poziomu dla klasy rozwiązań powtarzającego się problemu.
Doc Brown,
@DocBrown: Racja, ale czy operacje matematyczne, zapisywanie obiektów do pliku, wyjście graficzne lub kodowanie danych nie powracają, tak jak iteracja?
Frank Puffer
Wybór Wzorca Projektowego jest subiektywny (tj. W oczach „projektantów” lub ludzi, którzy oceniają projekty). Nazewnictwo wzorców projektowych ma być niezależne od domeny (abyśmy nie rozproszyli się myśleniem, że jest on specyficzny dla domeny). Po prostu moja opinia, że ​​nie mam cytowanych referencji.
rwong
@FrankPuffer Jeśli zarysujesz typowe rozwiązanie do zapisywania obiektów w pliku, możesz zapisać swoje rozwiązanie i nazwać go Wzorem Zapisywania Obiektów, jeśli jest to pomocne.
Brandin
3
Przemyślasz to. Wzorzec projektowy jest dobrze znanym rozwiązaniem typowego problemu komputerowego i to wszystko. Korzystasz ze wzoru, kiedy możesz rozpoznać i zastosować wynikające z niego korzyści.
Robert Harvey

Odpowiedzi:

9

Większość wzorców z książki GoF ma następujące cechy wspólne:

  • rozwiązują podstawowe problemy projektowe za pomocą środków obiektowych
  • ludzie często napotykają tego rodzaju problemy w dowolnych programach, niezależnie od domeny lub firmy
  • są to przepisy, dzięki którym kod może być wielokrotnie wykorzystywany, często przez uczynienie go bardziej SOLIDNYM
  • przedstawiają kanoniczne rozwiązania tych problemów

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:

  • zapis do pliku - to znaczy, że IMHO po prostu nie jest wystarczająco „podstawowe”. To bardzo specyficzny problem. Nie ma też dobrego kanonicznego rozwiązania - istnieje wiele różnych podejść do pisania do pliku i nie ma wyraźnej „najlepszej praktyki”.
  • Malarz, Enkoder: cokolwiek masz na myśli, problemy te wydają mi się mniej podstawowe, a nawet niezależne od domeny.
  • posiadanie funkcji „mocy” dostępnej dla różnego rodzaju obiektów: na pierwszy rzut oka może być warte wykreślenia wzoru, ale proponowane rozwiązanie mnie nie przekonuje - wygląda to raczej na próbę zamiany funkcji mocy na coś podobnego do wzorzec iteratora. Zaimplementowałem dużo kodu z obliczeniami inżynierskimi, ale nie pamiętam sytuacji, w której pomogłoby mi podejście podobne do twojego obiektu funkcji mocy (jednak iteratory to coś, z czym muszę sobie radzić na co dzień).

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”.

Doktor Brown
ź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.
Robert Harvey
@RobertHarvey: Nie sądzę, aby wielu deweloperów wdrożyło dziś wzór iteratora w sposób „OO” sugerowany przez GoF. Zazwyczaj implementują go za pomocą języka lub standardowej biblioteki lib (na przykład w języku C # przy użyciu IEnumerablei yield). Ale w przypadku innych wzorców GoF to, co napisałeś, może być prawdopodobnie prawdą.
Doc Brown
1
Odpowiedni do workarounds for missing programming language features: blog.plover.com/prog/johnson.html
jrw32982 obsługuje Monikę
8

The Gang of Four cytuje definicję wzoru Christophera Alexandra:

Każdy wzorzec opisuje problem, który pojawia się wielokrotnie w naszym środowisku, a następnie opisuje rdzeń rozwiązania tego problemu […]

Jaki problem rozwiązują iteratory?

Cel: Zapewnij sposób dostępu do elementów obiektu agregującego sekwencyjnie, bez ujawniania jego podstawowej reprezentacji.

Zastosowanie: Użyj wzoru Iterator

  • aby uzyskać dostęp do zawartości obiektu agregującego bez ujawniania jego wewnętrznej reprezentacji
  • do obsługi wielu przejść obiektów agregowanych
  • aby zapewnić jednolity interfejs do przechodzenia przez różne struktury agregatów (to znaczy w celu wspierania iteracji polimorficznej).

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:

while (my ($key, $value) = each %$hash) {
  say "$key => $value";
}

Ale co, jeśli nazwiemy funkcję w ciele pętli?

while (my ($key, $value) = each %$hash) {
  do_something_with($key, $value, $hash);
}

Ta funkcja może teraz robić prawie wszystko, co chce, z wyjątkiem:

  • dodawaj lub usuwaj wpisy skrótu, ponieważ zmieniłyby one nieprzewidywalnie kolejność iteracji (w C ++ - mów, unieważniałyby iterator).
  • iteruj tę samą tabelę skrótów bez robienia kopii, ponieważ pochłonęłoby to ten sam stan iteracji. Ups

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>i Iterator<T>interfejs. Konkretne iterowalne jak ArrayList<T>tworzy szczególny rodzaj iteratora, podczas gdy HashSet<T>może dostarczyć zupełnie innego typu iteratora. To bardzo przypomina mi abstrakcyjny wzór fabryki, gdzie Iterable<T>jest abstrakcyjna fabryka, a Iteratorprodukt.

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!”

amon
źródło
Ładna odpowiedź, dzięki. Nadal nie jestem do końca przekonany, że to, co piszesz w ostatnim akapicie, ma tutaj zastosowanie. Zredagowałem swoje pytanie, aby wyjaśnić, co mam na myśli.
Frank Puffer