Wie verwende ich das Decorator-Muster, um großen Objekten kleine Funktionen hinzuzufügen?

8

Diese Frage betrifft die Verwendung des Decorator-Musters, um Objekten großer Klassen wenig Funktionalität hinzuzufügen.

Beachten Sie nach dem klassischen Decorator-Muster die folgende Klassenstruktur:

Geben Sie hier die Bildbeschreibung ein

Stellen Sie sich zum Beispiel vor, dies passiert innerhalb eines Spiels. Instanzen von ConcreteCharacterDecoratorsollen dem ConcreteCharacter"Wrapping" wenig Funktionalität hinzufügen .

Gibt beispielsweise methodA()einen intWert zurück, der den Schaden darstellt, den der Charakter Feinden zufügt. Das ConcreteCharacterDecoratorerhöht einfach diesen Wert. Daher muss nur Code hinzugefügt werden methodA(). Die Funktionalität von methodB()bleibt gleich.

ConcreteCharacterDecorator wird so aussehen:

class ConcreteCharacterDecorator extends AbstractCharacterDecorator{

    ConcreteCharacter character;

    public ConcreteCharacterDecorator(ConcreteCharacter character){
        this.character = character;
    }

    public int methodA(){
        return 10 + character.methodA();
    }

    public int methodB(){
        character.methodB(); // simply delegate to the wrapped object.
    }

}

Dies ist kein Problem bei kleinen Klassen mit zwei Methoden.

Aber was ist, wenn AbstractCharacter15 Methoden definiert sind? ConcreteCharacterDecoratormüsste sie alle implementieren, obwohl es nur dazu gedacht ist, wenig Funktionalität hinzuzufügen.

Am Ende werde ich eine Klasse haben, die eine Methode enthält, die ein wenig Funktionalität hinzufügt, und weitere 14 Methoden, die einfach an das innere Objekt delegieren.

Es würde so aussehen:

class ConcreteCharacterDecorator extends AbstractCharacterDecorator{

    ConcreteCharacter character;

    public ConcreteCharacterDecorator(ConcreteCharacter character){
        this.character = character;
    }

    public int methodA(){
        return 10 + character.methodA();
    }

    public int methodB(){
        character.methodB(); // simply delegate to the wrapped object.
    }
    public int methodC(){
        character.methodC(); // simply delegate to the wrapped object.
    }
    public int methodD(){
        character.methodD(); // simply delegate to the wrapped object.
    }
    public int methodE(){
        character.methodE(); // simply delegate to the wrapped object.
    }
    public int methodF(){
        character.methodF(); // simply delegate to the wrapped object.
    }
    public int methodG(){
        character.methodG(); // simply delegate to the wrapped object.
    }
    public int methodH(){
        character.methodH(); // simply delegate to the wrapped object.
    }
    public int methodI(){
        character.methodI(); // simply delegate to the wrapped object.
    }
    public int methodJ(){
        character.methodJ(); // simply delegate to the wrapped object.
    }
    public int methodK(){
        character.methodK(); // simply delegate to the wrapped object.
    }
    public int methodL(){
        character.methodL(); // simply delegate to the wrapped object.
    }
    public int methodM(){
        character.methodM(); // simply delegate to the wrapped object.
    }
    public int methodN(){
        character.methodN(); // simply delegate to the wrapped object.
    }
    public int methodO(){
        character.methodO(); // simply delegate to the wrapped object.
    }

}

Offensichtlich sehr hässlich.

Ich bin wahrscheinlich nicht der erste, der dieses Problem mit dem Dekorateur hat. Wie kann ich das vermeiden?

Aviv Cohn
quelle

Antworten:

6

Schnell und einfach: AbstractCharacterDelegatorhat keine abstrakten Funktionen, sondern virtuelle. Standardmäßig werden sie an das umschlossene Zeichen übergeben und können überschrieben werden. Dann erben Sie und überschreiben nur die wenigen, die Sie wollen.

Diese Art von Dingen ist nicht besonders nützlich, es sei denn, Sie machen einiges an Dekoration und Ihre gemeinsame Oberfläche ist besonders breit. Das sollte nicht zu häufig sein. Und dieser Ansatz ist vielleicht nicht der Dekorateur, aber er verwendet das Konzept und wird von Programmierern, die das Muster kennen, als Dekorateur verstanden.

Telastyn
quelle
Sie schlagen also vor, die Methoden in Java einfach in AbstractCharacterDelegator"regulär" zu setzen (da alle "regulären" Methoden in Java standardmäßig virtuell sind - was bedeutet, dass sie durch das Erben von Unterklassen überschrieben werden können)? Und lassen Sie ihre Implementierung einfach an das innere Objekt delegieren - und wenn ich das ändern möchte, überschreiben Sie sie einfach?
Aviv Cohn
Wenn ja, bedeutet dies, dass ich tatsächlich abstrakte Klassen und keine Schnittstellen verwenden muss, da Schnittstellen keine Implementierung in den Methoden zulassen. Ich werde also saubereren Code haben, aber keine Mehrfachvererbung mit Schnittstellen. Welches ist wichtiger?
Aviv Cohn
@Prog Ja, das sagt er. Die Verwendung einer Schnittstelle ist nicht ausgeschlossen. Wenn Sie zum UML-Diagramm zurückkehren, können Sie sich AbstractCharacterin ein verwandeln interface. Der Rest bleibt weitgehend gleich - ConcreteCharacterund AbstractCharacterDecoratorimplementieren Sie die Schnittstelle anstelle von Unterklassen. Alles sollte einfach funktionieren. Obwohl sie wahrscheinlich nichts anderes erweitern müssen, sind Ihre Dekorateure nicht an Unterklassen gebunden, AbstractCharacterDecoratorda sie die Delegierung immer auf die altmodische Weise durchführen können, wenn sie aus irgendeinem Grund jemals eine andere Klasse erweitern müssen.
Doval
@Doval Grundsätzlich wäre der beste Ansatz, AbstractCharacter eine Schnittstelle und alles andere konkrete oder abstrakte Klassen zu haben. Dies würde sauberen Code ermöglichen, wie Telastyn sagte, aber für mehr Flexibilität die Verwendung aller abstrakten Klassen. Recht?
Aviv Cohn
1
@Prog Ja, das fasst es ungefähr zusammen.
Doval
3

Wenn AbstractCharacter über 15 Methoden verfügt, muss es wahrscheinlich in kleinere Klassen unterteilt werden. Abgesehen davon gibt es nicht viel anderes, als dass die Sprache eine spezielle Syntaxunterstützung für die Delegierung bietet. Hinterlassen Sie einfach einen Kommentar, in dem die gesamte Delegation beginnt, damit der Leser weiß, dass er nicht weiter nach unten schauen muss. Was das Schreiben aller Weiterleitungsmethoden betrifft, kann Ihre IDE dort helfen.

Abgesehen davon bin ich der Meinung, dass Decorator am besten mit Schnittstellen funktioniert. Im Allgemeinen möchten Sie Ihre Abstraktion entweder auf eine endliche (möglicherweise 0) Anzahl von Unterklassen beschränken oder eine Schnittstelle verwenden. Unbegrenzte Vererbung hat alle Nachteile von Schnittstellen (Ihnen könnte eine falsche Implementierung übergeben werden), aber Sie erhalten nicht die volle Flexibilität (Sie können nur eine Unterklasse unterteilen). Schlimmer noch, es öffnet Sie für das Problem der fragilen Basisklasse und es ist nicht so flexibel wie Komposition (Sie erstellen SubclassA und SubclassB, können dann aber nicht von beiden erben, um SubclassAB mit Funktionen von beiden zu erstellen). Ich finde es also ein Kludge, in einen Erbbaum gezwungen zu werden, nur um die Dekoration zu unterstützen.

Doval
quelle
+1 für die Angabe des Kohäsionsproblems (zu viele Verantwortlichkeiten sind 15 Methoden) und die Verwendung von Schnittstellen.
Fuhrmanator
Manchmal kann es nicht in kleinere Klassen unterteilt werden, sagen wir eine Dateisystemklasse.
Vicente Bolea