Es gibt einige ähnliche Fragen 1 ,2 ,3 ,4 , aber nicht scheint genau der Fall in dieser Frage, noch scheinen die Lösungen optimal.
Dies ist eine allgemeine OOP-Frage, vorausgesetzt, Polymorphismus, Generika und Mixins sind verfügbar. Die tatsächlich zu verwendende Sprache ist OOP Javascript (Typescript), aber es ist das gleiche Problem in Java oder C ++.
Ich habe parallele Klassenhierarchien, die manchmal dasselbe Verhalten (Schnittstelle und Implementierung) aufweisen, aber manchmal hat jedes sein eigenes "geschütztes" Verhalten. Illustriert wie folgt:
Dies dient nur zu Illustrationszwecken . Es ist nicht das eigentliche Klassendiagramm. Um es zu lesen:
- Alles in der gemeinsamen Hierarchie (Mitte) wird zwischen der Canvas-Hierarchie (links) und der SVG-Hierarchie (rechts) geteilt. Mit Teilen meine ich sowohl Schnittstelle als auch Implementierung.
- Alles, was sich nur in der linken oder rechten Spalte befindet, bedeutet ein Verhalten (Methoden und Elemente), das für diese Hierarchie spezifisch ist. Beispielsweise:
- Sowohl die linke als auch die rechte Hierarchie verwenden genau dieselben Validierungsmechanismen, die als einzelne Methode (
Viewee.validate()
) in der gemeinsamen Hierarchie dargestellt werden. - Nur die Canvas-Hierarchie hat eine Methode
paint()
. Diese Methode ruft die Malmethode für alle untergeordneten Elemente auf. - Die SVG-Hierarchie muss die
addChild()
Methode von überschreibenComposite
, dies ist jedoch bei der Canvas-Hierarchie nicht der Fall.
- Sowohl die linke als auch die rechte Hierarchie verwenden genau dieselben Validierungsmechanismen, die als einzelne Methode (
- Die Konstrukte aus den beiden Seitenhierarchien können nicht gemischt werden. Eine Fabrik sorgt dafür.
Lösung I - Vererbung auseinander ziehen
Fowlers Tease Apart Inheritance scheint hier nicht den Job zu machen, da es einige Diskrepanzen zwischen den beiden Parallelen gibt.
Lösung II - Mixins
Dies ist die einzige, an die ich derzeit denken kann. Die beiden Hierarchien werden separat entwickelt, aber auf jeder Ebene mischen die Klassen die gemeinsame Klasse, die nicht Teil einer Klassenhierarchie ist. Das Weglassen der structural
Gabel sieht folgendermaßen aus:
Beachten Sie, dass sich jede Spalte in einem eigenen Namespace befindet, damit Klassennamen nicht in Konflikt geraten.
Die Frage
Kann jemand Fehler mit diesem Ansatz sehen? Kann sich jemand eine bessere Lösung vorstellen?
Nachtrag
Hier ist ein Beispielcode, wie dieser verwendet werden soll. Der Namespace svg
kann ersetzt werden durch canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Im Wesentlichen erstellen Clients zur Laufzeit eine Instanzhierarchie von Viewee-Unterklassen. wie so:
Angenommen, alle diese Viewees stammen aus der Canvas-Hierarchie. Sie werden durch Durchlaufen der Hierarchie gerendert, die paint()
jeden Viewee aufrufen kann. Wenn sie aus der SVG-Hierarchie stammen, wissen die Betrachter, wie sie sich dem DOM hinzufügen können, aber es gibt keine paint()
Durchquerung.
Antworten:
Der zweite Ansatz trennt Schnittstellen besser nach dem Prinzip der Schnittstellentrennung.
Ich würde jedoch eine Paintable-Oberfläche hinzufügen.
Ich würde auch einige Namen ändern. Keine Notwendigkeit, Verwirrung zu stiften:
quelle
Das stimmt eigentlich gar nicht. Typoskript hat einen wesentlichen Vorteil gegenüber Java, nämlich die strukturelle Typisierung. Sie können in C ++ etwas Ähnliches mit Vorlagen vom Typ Ente tun, aber es ist viel aufwändiger.
Definieren Sie grundsätzlich Ihre Klassen, aber erweitern oder definieren Sie keine Schnittstellen. Definieren Sie dann einfach die Schnittstelle, die Sie benötigen, und nehmen Sie sie als Parameter. Dann können die Objekte mit dieser Schnittstelle übereinstimmen - sie müssen es nicht wissen, um sie im Voraus zu erweitern. Jede Funktion kann genau und nur die Bits deklarieren, über die sie einen Scheiß geben, und der Compiler gibt Ihnen einen Pass, wenn der endgültige Typ ihn erfüllt, obwohl die Klassen diese Schnittstellen nicht explizit erweitern.
Dies befreit Sie von der Notwendigkeit, tatsächlich eine Schnittstellenhierarchie zu definieren und zu definieren, welche Klassen welche Schnittstellen erweitern sollen.
Definieren Sie einfach jede Klasse und vergessen Sie die Schnittstellen - die strukturelle Typisierung wird sich darum kümmern.
Beispielsweise:
Dies ist ein absolut legitimes Typoskript. Beachten Sie, dass die konsumierenden Funktionen nichts über die Basisklassen oder Schnittstellen wissen oder geben, die bei der Definition der Klassen verwendet werden.
Es spielt keine Rolle, ob die Klassen verwandt sind oder nicht. Es spielt keine Rolle, ob sie Ihre Schnittstelle erweitert haben. Definieren Sie einfach die Schnittstelle als Parameter, und fertig - alle Klassen, die sie erfüllen, werden akzeptiert.
quelle
validate()
Methode (deren Implementierung für beide identisch ist). dann nur svg.Viewee muss außer Kraft setzen addChild () , während nur canvas.Viewee braucht paint () (die Anrufe () Lack auf alle Kinder - , die ein Mitglied der Base sind Composite - Klasse). Ich kann mir das also nicht wirklich mit struktureller Typisierung vorstellen.SVGViewee.addChild()
,CanvasViewee
dass Sie genau dieselbe Funktion benötigen. Scheint mir also Sinn zu machen, dass beide von Composite inhärent sind?Schneller Überblick
Lösung 3: Das Software-Entwurfsmuster "Parallele Klassenhierarchie" ist Ihr Freund.
Lange erweiterte Antwort
Ihr Design begann richtig. Es kann optimiert werden, einige Klassen oder Mitglieder können entfernt werden, aber die Idee der "parallelen Hierarchie", die Sie anwenden, um ein Problem zu lösen, ist RICHTIG.
Beschäftige dich mehrmals mit demselben Konzept, normalerweise in Kontrollhierarchien.
Nach einer Weile beendete ich die gleiche Lösung wie andere Entwickler, die manchmal als "Parallel Hierarchy" -Designmuster oder "Dual Hierarchy" -Designmuster bezeichnet wird.
(1) Haben Sie jemals eine einzelne Klasse in eine einzelne Hierarchie von Klassen aufgeteilt?
(2) Haben Sie jemals eine einzelne Klasse in mehrere Klassen ohne Hierarchie aufgeteilt?
Wenn Sie diese vorherigen Lösungen separat angewendet haben, können Sie damit einige Probleme lösen.
Was aber, wenn wir diese beiden Lösungen gleichzeitig kombinieren?
Kombinieren Sie sie und Sie erhalten dieses "Designmuster".
Implementierung
Wenden wir nun das Software-Entwurfsmuster "Parallele Klassenhierarchie" auf Ihren Fall an.
Sie haben derzeit zwei oder mehr unabhängige Hierarchien von Klassen, die sehr ähnlich sind, ähnliche Assoziationen oder Absichten haben, ähnliche Eigenschaften oder Methoden haben.
Sie möchten vermeiden, dass Code oder Mitglieder dupliziert werden ("Konsistenz"), können diese Klassen jedoch aufgrund der Unterschiede zwischen ihnen nicht direkt zu einer einzigen zusammenführen.
Ihre Hierarchien sind dieser Abbildung also sehr ähnlich, es gibt jedoch mehr als eine:
In diesem noch nicht zertifizierten Entwurfsmuster werden MEHRERE ÄHNLICHE HIERARCHIEN ZU EINER EINZELNEN HIERARCHIE zusammengeführt, und jede gemeinsame oder gemeinsame Klasse wird durch Unterklassen erweitert.
Beachten Sie, dass diese Lösung komplex ist, da Sie bereits mit mehreren Hierarchien zu tun haben. Daher handelt es sich um ein komplexes Szenario.
1 Die Wurzelklasse
In jeder Hierarchie gibt es eine gemeinsame "Root" -Klasse.
In Ihrem Fall gibt es für jede Hierarchie eine unabhängige "Composite" -Klasse, die ähnliche Eigenschaften und ähnliche Methoden aufweisen kann.
Einige dieser Mitglieder können zusammengeführt werden, andere können nicht zusammengeführt werden.
Ein Entwickler kann also eine Basisstammklasse erstellen und den entsprechenden Fall für jede Hierarchie unterordnen.
In Abbildung 2 sehen Sie ein Diagramm nur für diese Klasse, in dem jede Klasse ihren Namespace behält.
Die Mitglieder sind inzwischen weggelassen.
Wie Sie vielleicht bemerken, befindet sich jede "zusammengesetzte" Klasse nicht mehr in einer separaten Hierarchie, sondern wird zu einer einzigen gemeinsamen oder gemeinsamen Hierarchie zusammengeführt.
Fügen wir dann die Mitglieder hinzu, die gleich sind, die in die Oberklasse verschoben werden können, und diejenigen, die unterschiedlich sind, zu jeder Basisklasse.
Und wie Sie bereits wissen, werden "virtuelle" oder "überladene" Methoden in der Basisklasse definiert, aber in den Unterklassen ersetzt. Wie Abbildung 3.
Beachten Sie, dass es möglicherweise einige Klassen ohne Mitglieder gibt und Sie möglicherweise versucht sind, diese Klassen zu entfernen, NICHT. Sie werden "Hollow Classes", "Enumerative Classes" und andere Namen genannt.
2 Die Unterklassen
Kehren wir zum ersten Diagramm zurück. Jede "Composite" -Klasse hatte in jeder Hierarchie eine "Viewee" -Unterklasse.
Der Vorgang wird für jede Klasse wiederholt. Beachten Sie, dass in Abbildung 4 die Klasse "Common :: Viewee" von "Common :: Composite" abstammt, der Einfachheit halber jedoch die Klasse "Common :: Composite" im Diagramm weggelassen wird.
Sie werden feststellen, dass "Canvas :: Viewee" und "SVG :: Viewee" NICHT MEHR von ihrem jeweiligen "Composite" abstammen, sondern stattdessen von dem gemeinsamen "Common :: Viewee".
Sie können die Mitglieder jetzt hinzufügen.
3 Wiederholen Sie den Vorgang
Der Prozess wird fortgesetzt. Für jede Klasse wird "Canvas :: Visual" nicht von "Canvas :: Viewee" abgeleitet, sondern von "Commons :: Visual". "Canvas :: Structural" wird nicht von "Canvas :: Viewee" abgeleitet ", buit von" Commons :: Structural "und so weiter.
4 Das 3D-Hierarchiediagramm
Sie erhalten eine Art 3D-Diagramm mit mehreren Ebenen. Die oberste Ebene hat die Hierarchie "Allgemein" und die unterste Ebene jede zusätzliche Hierarchie.
Ihre ursprünglichen unabhängigen Klassenhierarchien, in denen dies ähnlich ist (Abbildung 6):
Beachten Sie, dass einige Klassen weggelassen werden und die gesamte "Canvas" -Hierarchie der Einfachheit halber weggelassen wird.
Die endgültige integrierte Klassenhierarchie kann ungefähr so aussehen:
Beachten Sie, dass einige Klassen weggelassen werden und die gesamten "Canvas" -Klassen einfach weggelassen werden, jedoch den "SVG" -Klassen ähnlich sind.
Die "Common" -Klassen können als einzelne Ebene eines 3D-Diagramms dargestellt werden, die "SVG" -Klassen in einer anderen Ebene und die "Canvas" -Klassen in einer dritten Ebene.
Überprüfen Sie, ob jede Ebene mit der ersten verknüpft ist, in der jede Klasse eine übergeordnete Klasse der "Common" -Hierarchie hat.
Für die Code-Implementierung müssen möglicherweise entweder Schnittstellenvererbung, Klassenvererbung oder "Mixins" verwendet werden, je nachdem, was Ihre Programmiersprache unterstützt.
Zusammenfassung
Wie bei jeder Programmierlösung ist die Optimierung sehr wichtig. Eine schlechte Optimierung kann jedoch zu einem größeren Problem werden als das ursprüngliche Problem.
Ich empfehle weder "Lösung 1" noch "Lösung 2" anzuwenden.
In "Lösung 1" gilt dies nicht, da jeweils die Vererbung erforderlich ist.
"Lösung 2", "Mixins" können angewendet werden, jedoch nach dem Entwerfen der Klassen und Hierarchien.
Mixins sind eine Alternative für die schnittstellenbasierte Vererbung oder die klassenbasierte Mehrfachvererbung.
Meine vorgeschlagene Lösung 3 wird manchmal als Entwurfsmuster "Parallele Hierarchie" oder Entwurfsmuster "Doppelte Hierarchie" bezeichnet.
Viele Entwickler / Designer werden dem nicht zustimmen und glauben, dass es nicht existieren sollte. Aber ich habe mich selbst und andere Entwickler als gemeinsame Lösung für Probleme verwendet, wie die Ihrer Frage.
Noch etwas fehlt. In Ihren vorherigen Lösungen bestand das Hauptproblem nicht darin, "Mixins" oder "Interfaces" zu verwenden, sondern zunächst das Modell Ihrer Klassen zu verfeinern und später eine vorhandene Programmiersprachenfunktion zu verwenden.
quelle
canvas.viewee
und alle seine Nachkommen brauchen eine Methode namenspaint()
. Weder diecommon
noch diesvg
Klassen brauchen es. Aber in Ihrer Lösung befindet sich die Hierarchie incommon
, nichtcanvas
odersvg
wie in meiner Lösung 2. Wie genaupaint()
endet es also in allen Unterklassen,canvas.viewee
wenn dort keine Vererbung vorhanden ist?paint()
sollte in "canvas :: viewee" verschoben oder deklariert werden. Die allgemeine Musteridee bleibt erhalten, aber einige Mitglieder müssen möglicherweise verschoben oder geändert werden.canvas::viewee
?In einem Artikel mit dem Titel Entwurfsmuster für den Umgang mit Hierarchien mit doppelter Vererbung in C ++ stellt Onkel Bob eine Lösung namens Stairway to Heaven vor . Es ist erklärte Absicht:
Und das Diagramm zur Verfügung gestellt:
Obwohl in Lösung 2 keine virtuelle Vererbung vorhanden ist, entspricht sie weitgehend dem Muster „ Treppe zum Himmel“ . Somit erscheint Lösung 2 für dieses Problem vernünftig.
quelle