Was macht den Iterator zu einem Entwurfsmuster?

9

Ich habe mich gefragt, was den Iterator im Vergleich zu anderen ähnlichen Konstrukten so besonders macht, und das hat die Viererbande dazu gebracht, ihn als Entwurfsmuster aufzulisten.

Der Iterator basiert auf Polymorphismus (einer Hierarchie von Sammlungen mit einer gemeinsamen Schnittstelle) und der Trennung von Bedenken (das Durchlaufen der Sammlungen sollte unabhängig von der Art und Weise sein, wie die Daten strukturiert sind).

Was aber, wenn wir die Hierarchie von Sammlungen beispielsweise durch eine Hierarchie mathematischer Objekte (Ganzzahl, Gleitkomma, Komplex, Matrix usw.) und den Iterator durch eine Klasse ersetzen, die einige verwandte Operationen für diese Objekte darstellt, z. B. Potenzfunktionen. Das Klassendiagramm wäre das gleiche.

Wir könnten wahrscheinlich viele weitere ähnliche Beispiele wie Writer, Painter, Encoder und wahrscheinlich bessere finden, die auf die gleiche Weise funktionieren. Ich habe jedoch noch nie gehört, dass eines davon als Design Pattern bezeichnet wird.

Was macht den Iterator so besonders?

Ist es die Tatsache, dass es komplizierter ist, weil es einen veränderlichen Zustand zum Speichern der aktuellen Position innerhalb der Sammlung erfordert? Aber dann wird ein veränderlicher Zustand normalerweise nicht als wünschenswert angesehen.


Lassen Sie mich zur Verdeutlichung meines Punktes ein detaillierteres Beispiel geben.

Hier ist unser Designproblem:

Angenommen, wir haben eine Hierarchie von Klassen und eine Operation, die für die Objekte dieser Klassen definiert ist. Die Schnittstelle dieser Operation ist für jede Klasse gleich, die Implementierungen können jedoch völlig unterschiedlich sein. Es wird auch angenommen, dass es sinnvoll ist, die Operation mehrmals auf dasselbe Objekt anzuwenden, beispielsweise mit unterschiedlichen Parametern.

Hier ist eine sinnvolle Lösung für unser Entwurfsproblem (praktisch eine Verallgemeinerung des Iteratormusters):

Zur Trennung von Bedenken sollten die Implementierungen der Operation nicht als Funktionen zur ursprünglichen Klassenhierarchie (Operandenobjekte) hinzugefügt werden. Da wir die Operation mehrmals auf denselben Operanden anwenden möchten, sollte sie durch ein Objekt dargestellt werden, das einen Verweis auf den Operanden enthält, nicht nur durch eine Funktion. Daher sollte das Operandenobjekt eine Funktion bereitstellen, die das Objekt zurückgibt, das die Operation darstellt. Dieses Objekt bietet eine Funktion, die die eigentliche Operation ausführt.

Ein Beispiel:

Es gibt eine Basisklasse oder Schnittstelle MathObject(dummer Name, ich weiß, vielleicht hat jemand eine bessere Idee.) Mit abgeleiteten Klassen MyIntegerund MyMatrix. Für jede sollte MathObjecteine Operation Powerdefiniert werden, die die Berechnung von Quadrat, Würfel usw. ermöglicht. Also könnten wir schreiben (in Java):

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
quelle
5
"Das Klassendiagramm wäre das gleiche" - na und? Ein Entwurfsmuster ist kein Klassendiagramm. Es ist eine Abstraktion auf hoher Ebene für eine Klasse von Lösungen für ein wiederkehrendes Problem.
Doc Brown
@DocBrown: Richtig, aber nicht mathematische Operationen, das Schreiben von Objekten in eine Datei, die grafische Ausgabe oder das Codieren von wiederkehrenden Datenproblemen, genau wie bei der Iteration?
Frank Puffer
Die Wahl des Designmusters ist subjektiv (dh in den Augen von "Designern" oder den Personen, die Designs beurteilen). Die Benennung von Entwurfsmustern soll domänenunabhängig sein (damit wir nicht davon abgelenkt werden, zu denken, dass sie domänenspezifisch sind). Nur meine Meinung, ich habe keine Refs zu zitieren.
Rwong
@FrankPuffer Wenn Sie eine allgemeine Lösung zum Schreiben von Objekten in eine Datei skizzieren, können Sie Ihre Lösung aufschreiben und als Objektschreibmuster bezeichnen, wenn dies hilfreich ist.
Brandin
3
Du überdenkst das. Ein Entwurfsmuster ist eine bekannte Lösung für ein häufig auftretendes Computerproblem, und das ist alles. Sie verwenden das Muster, wenn Sie die Vorteile erkennen und anwenden können, die es bietet.
Robert Harvey

Antworten:

9

Die meisten Muster aus dem GoF-Buch haben Folgendes gemeinsam:

  • Sie lösen grundlegende Designprobleme mit objektorientierten Mitteln
  • Menschen sind häufig mit solchen Problemen in willkürlichen Programmen konfrontiert, unabhängig von der Domäne oder dem Geschäft
  • Sie sind Rezepte, um den Code wiederverwendbarer zu machen, oft indem er fester wird
  • Sie präsentieren kanonische Lösungen für diese Probleme

Die durch diese Muster gelösten Probleme sind so grundlegend, dass viele Entwickler sie hauptsächlich als Problemumgehung für fehlende Programmiersprachenfunktionen verstehen , was meiner Meinung nach eine gültige Sichtweise ist (beachten Sie, dass das GoF-Buch aus dem Jahr 1995 stammt, in dem Java und C ++ nicht so viele angeboten haben Funktionen wie heute).

Das Iteratormuster passt gut in diese Beschreibung: Es löst ein grundlegendes Problem, das sehr häufig auftritt, unabhängig von einer bestimmten Domäne, und wie Sie selbst geschrieben haben, ist es ein gutes Beispiel für die "Trennung von Bedenken". Wie Sie sicherlich wissen, finden Sie die direkte Iteratorunterstützung heutzutage in vielen zeitgenössischen Programmiersprachen.

Vergleichen Sie dies nun mit den Problemen, die Sie ausgewählt haben:

  • Schreiben in eine Datei - das ist meiner Meinung nach einfach nicht "grundlegend" genug. Es ist ein sehr spezifisches Problem. Es gibt auch keine gute kanonische Lösung - es gibt viele verschiedene Ansätze zum Schreiben in eine Datei und keine eindeutigen "Best Practices".
  • Maler, Encoder: Was auch immer Sie damit vorhaben, diese Probleme erscheinen mir noch weniger grundlegend und nicht einmal domänenunabhängig.
  • Die "Power" -Funktion für verschiedene Arten von Objekten verfügbar zu haben: Auf den ersten Blick könnte es sich lohnen, ein Muster zu sein, aber Ihre vorgeschlagene Lösung überzeugt mich nicht - es sieht eher nach einem Versuch aus, die Power-Funktion in etwas Ähnliches zu verwandeln das Iteratormuster. Ich habe viel Code mit technischen Berechnungen implementiert, kann mich aber nicht an eine Situation erinnern, in der mir ein Ansatz ähnlich Ihrem Power Function-Objekt geholfen hätte (Iteratoren sind jedoch etwas, mit dem ich mich täglich befassen muss).

Darüber hinaus sehe ich in Ihrem Power-Funktionsbeispiel nichts, was nicht als Anwendung des Strategiemusters oder des Befehlsmusters interpretiert werden könnte, was bedeutet, dass diese grundlegenden Teile bereits im GoF-Buch enthalten sind. Eine bessere Lösung könnte entweder Operatorüberladungs- oder Erweiterungsmethoden enthalten, aber diese Dinge unterliegen Sprachfunktionen, und genau das könnte die von der "Gang" verwendete "OO-Bedeutung" nicht bieten.

Doc Brown
quelle
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- Die Ironie ist, dass Softwareentwickler routinemäßig Software-Designmuster verwenden, die 20 Jahre alt sind, während sie immer noch glauben, dass sie Code auf dem neuesten Stand der Technik schreiben.
Robert Harvey
@RobertHarvey: Ich glaube nicht, dass viele Entwickler das Iteratormuster heute auf die vom GoF vorgeschlagene "OO-Art" implementieren würden. Sie implementieren es normalerweise mit den Mitteln der Sprache oder der Standardbibliothek (z. B. in C # mit IEnumerableund yield). Aber für andere GoF-Muster könnte das, was Sie geschrieben haben, wahrscheinlich wahr sein.
Doc Brown
1
Relevant für workarounds for missing programming language features: blog.plover.com/prog/johnson.html
jrw32982 unterstützt Monica
8

Die Viererbande zitiert Christopher Alexanders Musterdefinition:

Jedes Muster beschreibt ein Problem, das in unserer Umgebung immer wieder auftritt, und beschreibt dann den Kern der Lösung für dieses Problem […]

Was ist das Problem, das von Iteratoren gelöst wird?

Absicht: Bieten Sie eine Möglichkeit, nacheinander auf die Elemente eines Aggregatobjekts zuzugreifen, ohne dessen zugrunde liegende Darstellung verfügbar zu machen.

Anwendbarkeit: Verwenden Sie das Iteratormuster

  • Zugriff auf den Inhalt eines Aggregatobjekts, ohne dessen interne Darstellung verfügbar zu machen
  • um mehrere Durchläufe von aggregierten Objekten zu unterstützen
  • Bereitstellung einer einheitlichen Schnittstelle zum Durchlaufen verschiedener Aggregatstrukturen (dh Unterstützung der polymorphen Iteration).

Man könnte also argumentieren, dass das Iteratormuster per Definition domänenspezifisch für Sammlungen ist. Und das ist vollkommen in Ordnung. Andere Muster wie das Interpreter-Muster sind domänenspezifisch für domänenspezifische Sprachen, die Factory-Muster sind domänenspezifisch für die Objekterstellung,…. Dies ist natürlich ein ziemlich dummes Verständnis von "domänenspezifisch". Solange es sich um ein wiederkehrendes Problem-Lösungs-Paar handelt, können wir es als Muster bezeichnen.

Und es ist gut, dass das Iteratormuster existiert. Schlechte Dinge passieren, wenn Sie es nicht benutzen. Mein Lieblings-Anti-Beispiel ist Perl. Hier enthält jede Sammlung (Array oder Hash) den Iteratorstatus als Teil der Sammlung. Warum ist das so schlimm? Wir können leicht mit einer Weile über einen Hash iterieren - jede Schleife:

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

Was aber, wenn wir eine Funktion im Schleifenkörper aufrufen?

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

Diese Funktion kann jetzt so ziemlich alles tun, was sie will, außer:

  • Hinzufügen oder Löschen von Hash-Einträgen, da diese die Iterationsreihenfolge unvorhersehbar ändern würden (in C ++ - dies würde der Iterator ungültig werden).
  • Iterieren Sie dieselbe Hash-Tabelle, ohne eine Kopie zu erstellen, da dies denselben Iterationsstatus verbrauchen würde. Hoppla.

Wenn die aufgerufene Funktion den Iterator verwenden soll, wird das Verhalten unserer Schleife undefiniert. Das ist ein Problem. Das Iteratormuster hat eine Lösung: Platzieren Sie den gesamten Iterationsstatus in einem separaten Objekt, das pro Iteration erstellt wird.

Ja, natürlich ist das Iteratormuster mit anderen Mustern verwandt. Wie wird beispielsweise der Iterator instanziiert? In Java haben wir eine generische Iterable<T>und Iterator<T>Schnittstelle. Ein konkreter iterierbarer Typ ArrayList<T>erzeugt eine bestimmte Art von Iterator, während ein HashSet<T>möglicherweise einen völlig anderen Iteratortyp liefert. Das erinnert mich sehr an das abstrakte Fabrikmuster, bei dem Iterable<T>es sich um die abstrakte Fabrik und Iteratordas Produkt handelt.

Ein polymorpher Iterator kann auch als Beispiel für das Strategiemuster interpretiert werden. Beispielsweise kann ein Baum verschiedene Arten von Iteratoren anbieten (Vorbestellung, In-Reihenfolge, Nachbestellung,…). Extern würden diese alle eine Iteratorschnittstelle gemeinsam nutzen und Elemente in einer bestimmten Reihenfolge ergeben. Der Client-Code muss nur von der Iterator-Schnittstelle abhängen, nicht von einem bestimmten Tree-Traversal-Algorithmus.

Muster existieren nicht isoliert, unabhängig voneinander. Einige Muster sind unterschiedliche Lösungen für dasselbe Problem, und einige Muster beschreiben dieselbe Lösung in unterschiedlichen Kontexten. Einige Muster implizieren andere. Außerdem wird der Musterraum nicht geschlossen, wenn Sie die letzte Seite des Entwurfsmusterbuchs umblättern (siehe auch Ihre frühere Frage Hat die Viererbande den „Musterraum“ gründlich untersucht? ). Die im Buch Design Patterns beschriebenen Muster sind sehr flexibel und breit, offen für unendliche Variationen und definitiv nicht die einzigen existierenden Muster.

Die von Ihnen aufgelisteten Konzepte (Schreiben, Malen, Codieren) sind keine Muster, da sie keine Problem-Lösungs-Kombination beschreiben. Eine Aufgabe wie "Ich muss Daten schreiben" oder "Ich muss Daten verschlüsseln" ist kein wirkliches Entwurfsproblem und enthält keine Lösung. Eine „Lösung“, die nur aus „Ich weiß, ich werde eine Writer-Klasse erstellen“ besteht, ist bedeutungslos. Wenn wir jedoch ein Problem wie "Ich möchte nicht, dass halb gerenderte Grafiken auf den Bildschirm gemalt werden" haben, kann ein Muster vorhanden sein: "Ich weiß, ich werde doppelt gepufferte Grafiken verwenden!"

amon
quelle
Keine Antwort, danke. Ich bin immer noch nicht ganz davon überzeugt, dass das, was Sie im letzten Absatz schreiben, hier gilt. Ich habe meine Frage bearbeitet, um zu erklären, was ich meine.
Frank Puffer