Newbie Frage zu Decorator Design Pattern

18

Ich habe einen Programmierartikel gelesen, in dem das Decorator-Muster erwähnt wurde. Ich habe eine Weile lang programmiert, aber ohne irgendeine formale Ausbildung oder Schulung, aber ich versuche, etwas über die Standardmuster und dergleichen zu lernen.

Also habe ich den Decorator nachgeschlagen und einen Wikipedia-Artikel darüber gefunden. Ich verstehe jetzt das Konzept des Decorator-Musters, war aber etwas verwirrt über diese Passage:

Betrachten Sie als Beispiel ein Fenster in einem Fenstersystem. Um das Scrollen des Fensterinhalts zu ermöglichen, möchten wir ihm gegebenenfalls horizontale oder vertikale Bildlaufleisten hinzufügen. Angenommen, Fenster werden durch Instanzen der Window-Klasse dargestellt, und diese Klasse verfügt über keine Funktionalität zum Hinzufügen von Bildlaufleisten. Wir könnten eine Unterklasse von ScrollingWindow erstellen, die diese bereitstellt, oder wir könnten einen ScrollingWindowDecorator erstellen, der diese Funktionalität zu vorhandenen Window-Objekten hinzufügt. Zu diesem Zeitpunkt wäre jede Lösung in Ordnung.

Nehmen wir nun an, wir möchten auch die Möglichkeit haben, unseren Fenstern Rahmen hinzuzufügen. Auch hier hat unsere ursprüngliche Window-Klasse keine Unterstützung. Die ScrollingWindow-Unterklasse wirft nun ein Problem auf, da sie effektiv eine neue Art von Fenster erstellt hat. Wenn wir allen Fenstern Rahmenunterstützung hinzufügen möchten, müssen wir Unterklassen WindowWithBorder und ScrollingWindowWithBorder erstellen. Offensichtlich wird dieses Problem mit jeder neuen Funktion, die hinzugefügt wird, noch schlimmer. Für die Decorator-Lösung erstellen wir einfach einen neuen BorderedWindowDecorator. Zur Laufzeit können wir vorhandene Fenster mit dem ScrollingWindowDecorator oder dem BorderedWindowDecorator oder beiden nach Belieben dekorieren.

OK, wenn sie sagen, dass allen Fenstern Rahmen hinzugefügt werden sollen, warum nicht einfach der ursprünglichen Fensterklasse Funktionen hinzufügen, um die Option zuzulassen? Untergeordnete Klassen dienen meiner Ansicht nach nur dazu, einer Klasse bestimmte Funktionen hinzuzufügen oder eine Klassenmethode zu überschreiben. Wenn ich allen vorhandenen Objekten Funktionen hinzufügen müsste, warum sollte ich dann nicht einfach die Superklasse ändern?

Es gab eine andere Zeile im Artikel:

Das Dekorationsmuster ist eine Alternative zur Unterklasse. Unterklassen fügen Verhalten zur Kompilierungszeit hinzu, und die Änderung wirkt sich auf alle Instanzen der ursprünglichen Klasse aus. Dekorieren kann zur Laufzeit für einzelne Objekte ein neues Verhalten bereitstellen.

Ich verstehe nicht, wo sie sagen "... die Änderung wirkt sich auf alle Instanzen der ursprünglichen Klasse aus" - wie ändert die Unterklasse die übergeordnete Klasse? Ist das nicht der springende Punkt bei der Unterklassifizierung?

Ich gehe davon aus, dass der Artikel, wie viele Wikis, einfach nicht klar geschrieben ist. Ich kann den Nutzen des Decorators in der letzten Zeile sehen - "... zur Laufzeit neues Verhalten für einzelne Objekte bereitstellen."

Ohne dieses Muster gelesen zu haben, hätte ich, wenn ich das Verhalten zur Laufzeit für einzelne Objekte ändern müsste, wahrscheinlich einige Methoden in die Ober- oder Unterklasse eingebaut, um dieses Verhalten zu aktivieren / deaktivieren. Bitte helfen Sie mir, die Nützlichkeit des Dekorateurs wirklich zu verstehen, und warum ist mein Nachwuchsdenken fehlerhaft?

Jim
quelle
schätze die Bearbeitung, Walter ... fyi, ich habe "Jungs" nicht zum ausschließen von Frauen, sondern als informelle Begrüßung.
Jim
Die Bearbeitung würde nur darin bestehen, die Begrüßung im Allgemeinen zu entfernen. Es ist kein Standard-SO / SE-Protokoll, eines in Fragen zu verwenden [keine Sorge, wir halten es nicht für unhöflich, direkt in die Frage zu springen]
Farrell,
Cool, danke! es war nur so, dass er es als "Geschlecht entfernen" etikettierte und ich würde jeden hassen, der mich für frauenfeindlich hält oder so !! :)
Jim
Ich benutze sie stark, um die rechtlichen Verfahren zu vermeiden, die als "Dienstleistungen" bezeichnet werden: medium.com/@wrong.about/…
Zapadlo

Antworten:

13

Das Dekorationsmuster ist eines, das die Komposition gegenüber der Vererbung bevorzugt [ein weiteres OOP-Paradigma, über das man etwas lernen kann]

Der Hauptvorteil des Dekorationsmusters gegenüber Unterklassen besteht darin, dass mehr Mix & Match-Optionen möglich sind. Wenn Sie zum Beispiel 10 verschiedene Verhaltensweisen haben, die ein Fenster haben kann, bedeutet dies, dass Sie mit Unterklassen jede unterschiedliche Kombination erstellen müssen, was auch zwangsläufig eine Menge Wiederverwendung von Code mit sich bringt.
Was passiert jedoch, wenn Sie ein neues Verhalten hinzufügen möchten?

Mit dem Decorator fügen Sie einfach eine neue Klasse hinzu, die dieses Verhalten beschreibt, und das ist es - mit dem Muster können Sie dies effektiv einfügen, ohne den Rest des Codes zu ändern.
Mit der Subklassifizierung haben Sie einen Albtraum in den Händen.
Eine Frage, die Sie gestellt haben, war: "Wie ändert Unterklassen die übergeordnete Klasse?" Es ist nicht so, dass es die Elternklasse ändert. Wenn es sich um eine Instanz handelt, bedeutet dies jedes Objekt, das Sie "instanziiert" haben [wenn Sie Java oder C # verwenden, beispielsweise mit dem newBefehl]. Wenn Sie diese Änderungen zu einer Klasse hinzufügen, haben Sie keine andere Wahl, als dass diese Änderung vorhanden ist, auch wenn Sie sie nicht wirklich benötigen.

Alternativ können Sie alle seine Funktionen in einer einzelnen Klasse zusammenfassen, indem Sie sie über Flags aktivieren / deaktivieren. Dies führt jedoch zu einer einzelnen Klasse, die mit dem Wachstum Ihres Projekts immer größer wird.
Es ist nicht ungewöhnlich, Ihr Projekt auf diese Weise zu starten und es in ein Dekorationsmuster umzugestalten, sobald Sie eine effektive kritische Masse erreicht haben.

Ein interessanter Punkt, der angesprochen werden sollte: Sie können dieselbe Funktionalität mehrmals hinzufügen. So können Sie zum Beispiel ein Fenster mit doppelter, dreifacher oder einer beliebigen Anzahl oder Begrenzung nach Ihren Wünschen haben.

Der Hauptpunkt des Musters besteht darin, Laufzeitänderungen zu ermöglichen: Möglicherweise wissen Sie nicht, wie das Fenster aussehen soll, bis das Programm ausgeführt wird. Auf diese Weise können Sie es problemlos ändern. Zugegeben, dies kann über die Unterklasse erfolgen, ist aber nicht so schön.
Und schließlich können Klassen, die Sie möglicherweise nicht bearbeiten können, Funktionen hinzugefügt werden - beispielsweise in versiegelten / finalen Klassen oder in Klassen, die von anderen APIs bereitgestellt werden

Farrell
quelle
2
Danke, Farrell - wenn Sie sagen "Alternativ können Sie alle seine Funktionen über Flags in einer einzelnen Klasse ein- und ausschalten. Dies führt jedoch zu einer einzelnen Klasse, die mit dem Wachstum Ihres Projekts immer größer wird." das machte es für mich klicken. Ich denke, ein Teil des Problems ist, dass ich noch nie an einer Klasse gearbeitet habe, die so groß war, dass der Dekorateur für mich einen Sinn ergab. Wenn ich groß denke, kann ich definitiv die Vorteile erkennen ... danke!
Jim
Auch der Teil über versiegelte Klassen macht jede Menge Sinn ... danke!
Jim
3

Berücksichtigen Sie die Möglichkeiten, Bildlaufleisten und Rahmen mit Unterklassen hinzuzufügen. Wenn Sie alle Möglichkeiten nutzen möchten, erhalten Sie vier Klassen (Python):

class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)

In WindowWithScrollBarAndBorder.draw()wird das Fenster nun zweimal gezeichnet, und beim zweiten Mal wird die bereits gezeichnete Bildlaufleiste möglicherweise überschrieben, je nach Implementierung. Ihr abgeleiteter Code ist also eng an die Implementierung einer anderen Klasse gekoppelt, und Sie müssen sich jedes Mal darum kümmern, wenn Sie das WindowVerhalten der Klasse ändern . Eine Lösung wäre, den Code aus den übergeordneten Klassen in die abgeleiteten Klassen zu kopieren und an die Anforderungen der abgeleiteten Klassen anzupassen. Jede Änderung in einer übergeordneten Klasse muss jedoch erneut kopiert und angepasst werden, sodass die abgeleiteten Klassen erneut eingefügt werden eng an die Basisklasse gekoppelt (über die Notwendigkeit von Copy-Paste-Adjust). Ein weiteres Problem ist, dass Sie jede Klasse verdoppeln müssen, wenn Sie eine weitere Eigenschaft benötigen, die ein Fenster haben kann oder nicht:

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

Das heißt für eine Menge voneinander unabhängiger Merkmale f mit | f | Als Anzahl der Features müssen Sie 2 ** | f | definieren Klassen, zB. Wenn Sie 10 Funktionen haben, erhalten Sie 1024 eng begrenzte Klassen. Wenn Sie das Decorator-Muster verwenden, erhält jedes Feature seine eigene unabhängige und lose gekoppelte Klasse, und Sie haben nur 1 + | f | Klassen (das ist 11 für das obige Beispiel).

Pillmuncher
quelle
Sehen Sie, ich würde nicht weiter daran denken, Klassen hinzuzufügen - es wäre, die Funktionalität der ursprünglichen (Unter-) Klasse hinzuzufügen. Also im Klassenfenster (Objekt): Sie haben scrollBar = true, border = false, etc ... Die Antwort Farrell zeigt mir jedoch, wo das schief gehen kann - nur zu aufgeblähten Klassen und so ... Danke für die Antwort, +1!
Jim
Na ja, +1, sobald ich den Repräsentanten dazu habe!
Jim
2

Ich bin kein Experte für dieses spezielle Muster, aber meines Erachtens kann das Decorator-Muster auf Klassen angewendet werden, die Sie möglicherweise nicht modifizieren oder unterklassifizieren können (sie sind möglicherweise nicht Ihr Code und werden beispielsweise versiegelt) ). Was ist in Ihrem Beispiel, wenn Sie die Window-Klasse nicht geschrieben haben, sondern nur verbrauchen? Solange die Window-Klasse eine Schnittstelle hat und Sie gegen diese Schnittstelle programmieren, kann Ihr Decorator dieselbe Schnittstelle verwenden, aber die Funktionalität erweitern.

Das Beispiel , das Sie erwähnen , ist versichert hier ganz ordentlich:

Das Erweitern der Funktionalität eines Objekts kann statisch (zur Kompilierungszeit) mithilfe der Vererbung erfolgen. Es kann jedoch erforderlich sein, die Funktionalität eines Objekts dynamisch (zur Laufzeit) zu erweitern, wenn ein Objekt verwendet wird.

Betrachten Sie das typische Beispiel eines Grafikfensters. Wenn Sie die Funktionalität des Grafikfensters erweitern möchten, indem Sie beispielsweise dem Fenster einen Rahmen hinzufügen, müssen Sie die Fensterklasse erweitern, um eine FramedWindow-Klasse zu erstellen. Um ein gerahmtes Fenster zu erstellen, muss ein Objekt der FramedWindow-Klasse erstellt werden. Es wäre jedoch unmöglich, mit einem einfachen Fenster zu beginnen und dessen Funktionalität zur Laufzeit zu erweitern, um ein gerahmtes Fenster zu werden.

http://www.oodesign.com/decorator-pattern.html

Dan Diplo
quelle