2012-09-12 14 views
20

Ich habe das Dekorationsmuster aus dem Wiki http://en.wikipedia.org/wiki/Decorator_pattern gelesen, und Codebeispiel aus http://www.vincehuston.org/dp/decorator.html.Decorator Design Muster vs Vererbung?

Ich sehe den Punkt, dass die traditionelle Vererbung einem 'ist-a' Muster folgt, während der Dekorateur einem 'has-a' Muster folgt. Und die Aufrufkonvention des Dekorators sieht aus wie eine "Haut" über "Haut" .. über "Kern". z.B.

I* anXYZ = new Z(new Y(new X(new A))); 

wie im obigen Codebeispiel Link gezeigt.

Allerdings gibt es noch ein paar Fragen, die ich nicht verstehe:

  1. , was unter ‚Das Decorator-Muster bedeutet Wiki verwendet werden kann, um (dekorieren) zu erweitern die Funktionalität eines bestimmten Objektes bei Laufzeit '? das 'neue ... (neu ... (neu ...))' ist ein Laufzeitanruf und ist gut, aber ein 'AwithXYZ anXYZ;' ist eine Vererbung zur Kompilierzeit und ist schlecht?

  2. aus dem Code Beispiel Link kann ich sehen, dass die Anzahl der Klassendefinition ist fast das gleiche in beiden Implementierungen. Ich erinnere mich an Bücher in einem anderen Designmuster wie "Head first design patterns". Sie verwenden als Beispiel Sternbuzz Kaffee und sagen, dass traditionelle Vererbung eine 'Klassenexplosion' Ursache für jede Kombination von Kaffee verursachen wird, würden Sie mit einer Klasse dafür kommen. Aber ist es nicht das gleiche für Dekorateur in diesem Fall? Wenn eine Dekorateur Klasse jede abstrakte Klasse nehmen und sie dekoriert, dann denke ich es Explosion nicht verhindern, sondern aus dem Code Beispiel: Sie haben genaue Anzahl der Klassendefinitionen, nicht weniger ...

Könnte jemand helfen erklären?

Vielen Dank,

+0

weiß [Wann sollten wir Dekorateur Entwurfsmuster verwenden und nicht die Vererbung] (http: // www. singhajit.com/decorator-design-pattern/) –

Antwort

65

Nehmen wir einige abstrakte Streams zum Beispiel und stellen Sie sich vor, Sie möchten über sie Verschlüsselungs- und Komprimierungsdienste bereitstellen.

Mit Dekorateur Sie haben (Pseudocode):

Stream plain = Stream(); 
Stream encrypted = EncryptedStream(Stream()); 
Stream zipped = ZippedStream(Stream()); 
Stream zippedEncrypted = ZippedStream(EncryptedStream(Stream()); 
Stream encryptedZipped = EncryptedStream(ZippedStream(Stream()); 

Mit Vererbung, Sie haben:

class Stream() {...} 
class EncryptedStream() : Stream {...} 
class ZippedStream() : Stream {...} 
class ZippedEncryptedStream() : EncryptedStream {...} 
class EncryptedZippedStream() : ZippedStream {...} 

1) mit Dekorateur, kombinieren Sie die Funktionalität zur Laufzeit, je nach Ihren Bedürfnissen. Jede Klasse kümmert sich nur um eine Facette der Funktionalität (Komprimierung, Verschlüsselung, ...)

2) In diesem einfachen Beispiel haben wir 3 Klassen mit Dekoratoren und 5 mit Vererbung. Fügen wir nun einige weitere Dienste hinzu, z. Filtern und Clippen. Mit Decorator brauchen Sie nur noch 2 Klassen um alle möglichen Szenarien zu unterstützen, z. Filterung -> Clipping -> Komprimierung -> Beschreibung. Bei der Vererbung müssen Sie für jede Kombination eine Klasse angeben, sodass Sie Dutzende von Klassen erhalten.

+1

Danke Zdeslav. Eine großartige Erklärung, wie Dekorator verwendet wird und keine Explosion verursacht! – user1559625

+0

Gern geschehen :) –

+0

sehr nette Antwort !! wirklich nett – Danyal

1

den zweiten Teil Ihrer Frage zu adressieren, mit der Dekorateur Methode, die Sie Zugriff (was wiederum könnte Ihre ersten Teil-Adresse) auf die gleiche Anzahl von Kombinationen, aber nicht muss sie schreiben. Wenn Sie auf jeder Ebene 3 Ebenen mit Dekoratoren mit 5 Optionen haben, können Sie mit Hilfe der Vererbung 5*5*5 mögliche Klassen definieren. Mit der Decorator-Methode benötigen Sie 15.

1

Zunächst einmal, ich bin ein C# Person und habe nicht mit C++ in einer Weile behandelt, aber hoffentlich bekommen Sie, wo ich herkomme.

Ein gutes Beispiel, das in den Sinn kommt, ist ein DbRepository und ein CachingDbRepository:

public interface IRepository { 
    object GetStuff(); 
} 

public class DbRepository : IRepository { 

    public object GetStuff() { 
    //do something against the database 
    } 
} 

public class CachingDbRepository : IRepository { 
    public CachingDbRepository(IRepository repo){ } 

    public object GetStuff() { 
    //check the cache first 
    if(its_not_there) { 
     repo.GetStuff(); 
    } 
} 

Also, wenn ich Erbschaft nur verwendet, habe ich eine DbRepository und CachingDbRepository haben würde. Die DbRepository würde von einer Datenbank abfragen; Die CachingDbRepository würde ihren Cache überprüfen und wenn die Daten nicht da wären, würde sie eine Datenbank abfragen. Es gibt also eine mögliche Doppelimplementation hier.

Durch die Decorator-Muster verwenden, ich habe immer noch die gleiche Anzahl von Klassen, aber meine CachingDbRepository nimmt in einem IRepository und ruft seine GetStuff() die Daten aus dem zugrunde liegenden Repo zu erhalten, wenn es nicht im Cache ist.

Also die Anzahl der Klassen sind gleich, aber die Verwendung der Klassen sind verwandt.CachingDbRepo ruft den Repo auf, der in ihn eingegeben wurde ... also ist es eher eine Komposition über Vererbung.

Ich finde es subjektiv wann zu entscheiden, wann nur Vererbung über Dekoration zu verwenden.

Ich hoffe, das hilft. Viel Glück!

7

In umgekehrter Reihenfolge:

2) mit, sagen wir, 10 verschiedene unabhängige Erweiterungen, eine beliebige Kombination von denen zur Laufzeit benötigt werden könnten, 10 Dekorateur Klassen wird die Arbeit machen. Um alle Möglichkeiten durch Vererbung abzudecken, benötigen Sie Unterklassen. Und es würde keine Möglichkeit geben, massive Code-Redundanz zu umgehen.

1) Stellen Sie sich vor, Sie hätten diese 1024 Unterklassen zur Laufzeit zur Auswahl. Versuchen Sie, den benötigten Code zu skizzieren. Denken Sie daran, dass Sie möglicherweise nicht in der Lage sind, die Reihenfolge zu bestimmen, in der Optionen ausgewählt oder abgelehnt werden. Denken Sie auch daran, dass Sie möglicherweise eine Instanz für eine Weile verwenden müssen, bevor Sie sie erweitern. Mach weiter, versuch es. Mit Dekorateuren zu tun ist im Vergleich trivial.

+0

Vielen Dank, Beta. – user1559625

+0

* 1023, falls die Reihenfolge keine Rolle spielt. – Caleb

3

Sie haben Recht, dass sie manchmal sehr ähnlich sein können. Die Anwendbarkeit und die Vorteile beider Lösungen hängen von Ihrer Situation ab.

Andere haben mich zu angemessenen Antworten auf Ihre zweite Frage geschlagen. Kurz gesagt, Sie können Dekoratoren kombinieren, um mehr Kombinationen zu erzielen, die Sie nicht mit der Vererbung machen können.

Als solche ich auf dem ersten Schwerpunkt:

Sie können nicht unbedingt sagen, Compiler-schlecht ist und die Laufzeit ist gut, es nur verschiedene Flexibilität. Die Möglichkeit, Dinge zur Laufzeit zu ändern, kann für einige Projekte wichtig sein, da Änderungen ohne erneute Kompilierung möglich sind, die langsam sein können und eine Umgebung erfordern, in der Sie kompilieren können.

Ein Beispiel, in dem Sie keine Vererbung verwenden können, ist, wenn Sie einem instanziierten Objekt Funktionalität hinzufügen möchten.Angenommen, Sie sind eine Instanz eines Objekts zur Verfügung gestellt, die eine Logging-Schnittstelle implementiert:

public interface ILog{ 
    //Writes string to log 
    public void Write(string message); 
} 

Angenommen, Sie eine komplizierte Aufgabe beginnen, die viele Objekte und jeder von ihnen beinhalten nicht die Anmeldung, so dass Sie auf dem Protokollobjekt übergeben. Sie möchten jedoch, dass jeder Nachricht von der Aufgabe der Name und die Aufgaben-ID des Tasks vorangestellt wird. Sie könnten eine Funktion weitergeben oder den Namen und die ID weitergeben und jedem Anrufer vertrauen, dass er der Regel der vorherigen Informationen folgt, oder Sie können das Protokollierungsobjekt dekorieren, bevor Sie es weitergeben, und sich nicht um die anderen Objekte kümmern müssen es richtig

public class PrependLogDecorator : ILog{ 

    ILog decorated; 

    public PrependLogDecorator(ILog toDecorate, string messagePrefix){ 
     this.decorated = toDecorate; 
     this.prefix = messagePrefix; 
    } 

    public void Write(string message){ 
     decorated.Write(prefix + message); 
    } 
} 

Leider über den Code C#, aber ich denke, es wird immer noch die Ideen mit jemandem kommunizieren, die C++

+1

Danke vossad01. Ihre Anmerkungen zu "Funktionalität zu einem instanziierten Objekt hinzufügen" ist wirklich gut! – user1559625

+0

+1 für "Ein Beispiel, in dem Sie keine Vererbung verwenden können, ist, wenn Sie einem instanziierten Objekt Funktionalität hinzufügen möchten." –