Feb 24

Design Patterns: Dekorierer (Decorator) [vs. Vererbung]

Tag: AllgemeinPhoscur @ 18:10

Hab ja schon lange nichts mehr geschrieben, greife nun den Gedanken etwas über Design Patterns zu schreiben wieder auf.

Vorerst muss ich darauf hinweisen, dass ich kein Profi bin und hier meine subjektive Meinung vertrete. Dies wird also kein Eintrag wie aus dem Lehrbuch, ich versuche nur etwas auf meine Weise klar zu machen.

Einleitung der GoF: Favorisiere Zusammensetzung vor Vererbung („Favor object composition over class inheritance“)

Vererbung sollte für jeden, der schon mal ein paar Klassen geschrieben hat, klar sein; Stichwort dazu ist (in PHP) „extends“. Komposition ist schon ein wenig schwieriger. Die wichtigste Rolle spielt hier der Dekorierer.

Hier nun ein bischen PHP-Code, hoffentlich schon selbsterklärend:

interface Etwas
{
public function tuWas();
}
class EtwasImplementation implements Etwas
{
public function tuWas()
{
return 'Etwas tut was.';
}
}
class AnderesEtwas implements Etwas
{
public function tuWas()
{
return 'Dieses Etwas tut was Anderes, evtl. Ähnliches auf andere Weise.';
}
}
class EtwasDekorierer implements Etwas
{
protected $_Etwas;
public function __construct(Etwas $Etwas)
{
$this->_Etwas = $Etwas;
}
public function tuWas()
{
return $this->_Etwas->tuWas().' Und der Dekorierer verarbeitet das noch weiter, kann jedes Etwas erweitern.';
}
}

Wie man sieht ist der EtwasDekorierer selbst ein Etwas, kann auch jeder Zeit die Stelle eines Etwas einnehmen, wie als würde er davon abstammen (Vererbung). Der klare Vorteil gegenüber der Vererbung ist nun, dass der EtwasDekorierer sowohl die EtwasImplementation als auch AnderesEtwas erweitern kann. Das, was erweitert wird, wird dadurch austauschbar. Mehrere Dekorierer lassen sich übrigens hervorragend verschachteln.

Hier noch ein kleines, etwas konkreteres Beispiel:

class Logger # soll Methodenaufrufe des dekorierten Objekts loggen.
{
    protected $_Object;
    public function __construct($Object) #ggf prüfen ob es ein Objekt eines bestimmten Typs ist (per Typehint)
    {
        $this->_Object = $Object;
    }
    public function __call($method, $args);
    {
         printf('Methode %s eines Objekts der Klasse %s wurde aufgerufen \n', $method, get_class($this->_Object));
         # Hier kann man natürlich je nach Bedarf etwas Anderes einbauen... (e.g. In Datei schreiben etc.etc..)
         return call_user_func_array(array($this->_Object, $method), $args);
    }
}

Der Nachteil am Dekorierer ist, dass man alles delegieren, also weiterleiten, muss. Besonders wenn der Dekorierer nur einen Teil der Methoden verändern soll ist das nervig alle Methoden an das dekorierte Objekt weiterzuleiten. PHP5s __call() kann da praktisch sein, obwohl dann zu überdenken ist, ob der Dekorierer nicht unnötig ist, gerade in PHP, wo OOP weniger performant ist.

Insgesamt wird mit dem Dekorierer eine weniger feste Bindung zwischen den Objekten gelegt, dadurch wird das Design flexibler.

6 Kommentare zu “Design Patterns: Dekorierer (Decorator) [vs. Vererbung]”

  1. Phoscur schrieb:

    Bekomme den Syntaxhighlighter irgendwie nicht zum laufen, sorry. Ah scheint nun zu gehen, weiß aber nicht warum^^

  2. Phoscur schrieb:

    Update: Konkretes Beispiel

  3. JÜRGEEEEEN schrieb:

    ich versteh den Sinn von Interfacen vielleicht einfach nicht, aber da das Interface nur vorgibt, welche Funktionen eigentlich da sein müssten, macht das vielleicht bei ganz weiter Aufteilung des Gebietes vom Design her leichter, von der Performance ist das aber äußerst überflüssig, da kommt man mit Vererbung doch deutlich leichter und mit meist weniger Arbeit davon, da man alle relevanten Funktionen belegt, den Rest einfach bei der dummy-funktion belassen kann

  4. Phoscur schrieb:

    Mir ist vor kurzem aufgegangen, was „Favor object composition over class inheritance“ bedeutet, ist aber eine längere Geschichte, da steigst du vielleicht auch irgendwann durch..
    Jedenfalls ist Vererbung nur selten die richtige oder beste Lösung, Interfaces sind für den besseren Weg sehr gut geeignet. Vielleicht fängst du mal bei „test driven“ an, dabei spielen Interfaces eine große Rolle, weil sie das erste sind, was man schreibt.
    Auf Performance kann man nicht immer achten, vor allem wenn man OOP Ansätze verfolgt. Ich verzichte oft auf überflüssige Klassen, die man in Java vielleicht anlegen würde, um Performance zu sparen. Letztlich ist die Architektur aber wichtiger bei den Zielen dich ich verfolge, die Performance hat leider manchmal das Nachsehen. Ich denke aber, dass das Endprodukt trotzdem sehr schnell sein wird, vor allem weil ich einen Großteil auf JavaScript umlegen werde.

  5. nikosch schrieb:

    » ob das auch wirklich die perfekte Übersetzung für Composition ist.
    Die lautet schlicht „Komposition“
    » Die wichtigste Rolle spielt hier der Dekorierer.
    Eigentlich nicht. Falls Du von der Composition redest. Dekorieren heißt, das Objekt implementiert das gleiche Interface (und erweitert dieses evtl) und leitet gewisse Methodenaufrufe auf das komponierte Objekt weiter (vice versa dessen Rückgaben). Decorators sind aber nur eine Kompositionsform unter vielen. Genau genommen ist es sogar eine Aggregation, da assoziierte Objekte durchaus ohne den umschließenden Decorator existieren können.

  6. Phoscur schrieb:

    Danke für den Kommentar. Der Artikel ist recht alt und spiegelt nur meine ersten Erkenntnisse zu Entwurfsmustern wieder. Mittlerweile habe ich auch das GoF-Buch gelesen und weit mehr Erfahrung. Vielleicht überarbeite ich den Artikel demnächst mal und bringe ihn sprachlich und inhaltlich auf eine professionellere Ebene.

Dein Kommentar

Du musst eingelogt sein, um einen Kommentar zu schreiben.