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 MyInteger
und MyMatrix
. Für jede sollte MathObject
eine Operation Power
definiert 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
quelle
Antworten:
Die meisten Muster aus dem GoF-Buch haben Folgendes gemeinsam:
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:
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.
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.IEnumerable
undyield
). Aber für andere GoF-Muster könnte das, was Sie geschrieben haben, wahrscheinlich wahr sein.workarounds for missing programming language features:
blog.plover.com/prog/johnson.htmlDie Viererbande zitiert Christopher Alexanders Musterdefinition:
Was ist das Problem, das von Iteratoren gelöst wird?
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:
Was aber, wenn wir eine Funktion im Schleifenkörper aufrufen?
Diese Funktion kann jetzt so ziemlich alles tun, was sie will, außer:
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>
undIterator<T>
Schnittstelle. Ein konkreter iterierbarer TypArrayList<T>
erzeugt eine bestimmte Art von Iterator, während einHashSet<T>
möglicherweise einen völlig anderen Iteratortyp liefert. Das erinnert mich sehr an das abstrakte Fabrikmuster, bei demIterable<T>
es sich um die abstrakte Fabrik undIterator
das 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!"
quelle