2016-06-01 21 views
2

Composite pattern ist nützlich für die Behandlung von Teil-Ganzes-Hierarchien. Es hat eine Schnittstelle für die Component. Leaf und Composite beide bieten Implementierung für die Component Schnittstelle.Wie behandelt man Funktionen zum Hinzufügen, Löschen in einem zusammengesetzten Muster?

Was sollte die Implementierung für Add(Component c), Remove(Component c) und GetChild(int position) Methoden in Leaf Klasse sein?

kann ich Methoden nichts tun oder eine Ausnahme werfen wie: OperationNotSuportedByLeafException. Aber damit bricht das Liskov-Substitutionsprinzip. Was ist der beste Weg, um mit diesen Methoden umzugehen?

bearbeiten: Ein anderer Ansatz ist diese Methoden in Composite- bewegen. Es wird die oberste Schnittstelle sein, d. H. Die Komponente. Das Verschieben der Methoden in Composite erfordert explizites Casting, wenn die add, remove Operationen aufgerufen werden müssen, was wiederum gegen ein gutes Designprinzip ist.

enter image description here

Antwort

1

Es gibt zwei Möglichkeiten, dieses Problem zu lösen.

  1. Werfen OperationNotSuportedByLeafException. Sie können debattieren, ob dies LSP bricht. Persönlich denke ich, dass es vermieden werden sollte, aber manchmal ist es die beste Lösung (siehe Java unveränderliche Listen zum Beispiel). Das LSP ist ein Prinzip, das Ihnen helfen soll, besseren Qualitätscode zu schreiben. Jedes System hat Warzen und das kann einer von ihnen sein.

  2. Verschieben hinzufügen & entfernen zu Composite entity (example). Dies ist, was Sie normalerweise in einer Ansichts-Bibliothek sehen würden. Eine Ansicht kann ein Textblock oder ein komplexes Layout mit vielen anderen Ansichten sein.

+0

# 2. Sie können nichts zu/von einem Blatt hinzufügen oder entfernen - verschieben Sie den Code in Composite. – dbugger

+0

@croxis, ja 2. Option ist auch ein Weg, und ich werde meine Frage bearbeiten, um das auch zu integrieren. Aber es hat auch ein Problem, es wird die Component-Schnittstelle sein, die ausgesetzt wird, und um die Operation add, remove aufzurufen, wird eine explizite Typumwandlung benötigt und wiederum gegen gute Designprinzipien. –

+1

@ nits.kk Warum ist explizites Typecasting erforderlich? Das ist nicht für das Muster erforderlich (möglicherweise für Ihr Design, wenn Sie Ihre Frage mit mehr Details aktualisieren). Trotzdem werden Sie oft feststellen, dass gute Designprinzipien miteinander in Konflikt geraten können. Nicht jedes Problem hat eine saubere Lösung, manchmal wählen Sie das unordentlichste. – cyroxis

2

Hängt natürlich davon ab, was Ihre Designziele sind. Wenn Ihr Ziel ist, dass der Baum/Graph zu irgendeinem Zeitpunkt modifizierbar sein sollte, dann kann ein Blatt tatsächlich ein Elternknoten werden. In diesem Fall ist es in Ordnung, hierarchierelevante Methoden in der Komponente zu definieren.

Ein anderer Ansatz (obwohl ich weiß nicht, ob dies auf Ihren Anwendungsfall anwendbar ist) ist folgende zwei Ideen zu nutzen:

  • die Struktur unveränderlich
  • von Funktion Separate Struktur
  • Machen

Indem wir die Struktur unveränderlich machen, können wir alle Graphenkonstruktionen auf die Konstruktoren schieben. Damit umgehen wir die von Ihnen erwähnte Typ-Casting-Problematik und machen das Ganze leichter verständlich.

Durch die Trennung der Struktur von der Funktion müssen wir die Strukturinformationen überhaupt nicht an Kunden veröffentlichen, stattdessen bieten wir die Funktionalität, die wir anbieten möchten. Damit können wir Liskov, Demeter und die anderen OO-Sachen behalten.

Dies ist, wie es aussehen kann wie:

public interface Node { 
    // Here we offer the "functionality", but we don't 
    // publish the "structure". This is made-up, I 
    // don't know your use-case. 
    void process(Consumer<Payload> payloadConsumer); 
} 

public class Leaf implements Node { 
    private Payload payload; 

    public Lead(Payload payload) { 
     this.payload = payload; 
    } 

    @Override 
    public void process(Consumer<Payload> payloadConsumer) { 
     payloadConsumer.accept(payload); 
    } 
} 

public class ParentNode implements Node { 
    private List<Node> children; 

    public ParentNode(Node... children) { 
     this.children = asList(children); 
    } 

    // Here we implement the processing recursively. 
    // All children can respond according to their semantics, 
    // instead of assuming any structure beyond what we know. 
    @Override 
    public void process(Consumer<Payload> payloadConsumer) { 
     children.forEach(child -> child.process(payloadConsumer)); 
    } 
} 

Natürlich können Sie Ihre eigene Node Arten je nachdem, welche Art von Logik definieren können Sie darstellen möchten. Sie können mehrere Operationen definieren, nicht nur die eine process() Methode, die ich gemacht habe. Dann können Sie alles so zusammenstecken:

+0

Mir gefiel die Idee, die Blätter in Cunstructor of Composite zu addieren. Aber normalerweise in ui basierten Anwendungsfällen, können wir beispielsweise TextView, Button, Label (jedes Blatt) und andere Ansichten wie LinearLayout, RelativeLayout (jeweils vom Typ Composite) haben, in solchen Fällen werden Blätter niemals Composite, sondern ges at run Zeitblätter und Composites können zu einem übergeordneten Composite hinzugefügt oder daraus entfernt werden. Daher wird Konstruktor Ansatz nicht funktionieren –

+0

Großartiges Beispiel! Aus Neugier, warum nennst du es einen 'Consumer' anstatt einen' Visitor'? – cyroxis

+0

Nur weil 'java.util.function.Consumer' bereits vorhanden ist und dem Zweck dieses Beispiels entspricht. –