345 Stimmen

Wann sollte ich das Visitor Design Pattern verwenden?

Ich sehe immer wieder Hinweise auf das Besuchermuster in Blogs, aber ich muss zugeben, dass ich es nicht verstehe. Ich lese die wikipedia Artikel für das Muster und ich verstehe seine Funktionsweise, aber ich bin immer noch verwirrt, wann ich es verwenden würde.

Als jemand, der erst kürzlich wirklich hat das Dekorationsmuster und sieht nun überall Anwendungen dafür. Ich möchte dieses scheinbar praktische Muster auch wirklich intuitiv verstehen können.

327voto

Federico A. Ramponi Punkte 44697

Ich bin nicht sehr vertraut mit dem Besuchermuster. Mal sehen, ob ich es richtig verstanden habe. Angenommen, Sie haben eine Hierarchie von Tieren

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Nehmen wir an, es handelt sich um eine komplexe Hierarchie mit einer gut etablierten Schnittstelle).

Jetzt wollen wir eine neue Operation in die Hierarchie einfügen, nämlich dass jedes Tier seinen Ton macht. Solange die Hierarchie so einfach ist, kann man das mit einfachem Polymorphismus machen:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

Wenn Sie jedoch so vorgehen, müssen Sie jedes Mal, wenn Sie eine Operation hinzufügen wollen, die Schnittstelle zu jeder einzelnen Klasse der Hierarchie ändern. Nehmen Sie stattdessen an, dass Sie mit der ursprünglichen Schnittstelle zufrieden sind und sie so wenig wie möglich ändern wollen.

Das Visitor-Muster ermöglicht es Ihnen, jede neue Operation in eine geeignete Klasse zu verschieben, und Sie müssen die Schnittstelle der Hierarchie nur einmal erweitern. Gehen wir es an. Zunächst definieren wir eine abstrakte Operation (die Klasse "Visitor" in GoF ), die für jede Klasse in der Hierarchie eine Methode hat:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Dann ändern wir die Hierarchie, um neue Operationen zu akzeptieren:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Schließlich wird die eigentliche Operation durchgeführt, ohne weder Katze noch Hund zu ändern :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Jetzt haben Sie die Möglichkeit, Operationen hinzuzufügen, ohne die Hierarchie weiter zu verändern. So funktioniert es:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

143voto

Konrad Rudolph Punkte 503837

Der Grund für Ihre Verwirrung ist wahrscheinlich, dass der Begriff "Besucher" eine fatale Fehlbezeichnung ist. Viele (prominente 1 !) sind die Programmierer über dieses Problem gestolpert. Was es tatsächlich tut, ist die Implementierung doppelte Disposition in Sprachen, die dies nicht von Haus aus unterstützen (was bei den meisten der Fall ist).


1) Mein Lieblingsbeispiel ist Scott Meyers, der gefeierte Autor von "Effective C++", der dies als eine seiner Wichtigste C++ Aha! Momente immer .

94voto

Daniel C. Sobral Punkte 290004

Alle hier Anwesenden haben Recht, aber ich denke, dass das "Wann" nicht angesprochen wird. Erstens, von Design Patterns:

Mit Visitor können Sie eine neue Operation definieren, ohne die Klassen der Elemente, auf die sie wirkt, zu ändern.

Stellen wir uns nun eine einfache Klassenhierarchie vor. Ich habe die Klassen 1, 2, 3 und 4 und die Methoden A, B, C und D. Legen Sie sie wie in einer Tabellenkalkulation an: Die Klassen sind Zeilen und die Methoden sind Spalten.

Bei objektorientiertem Design geht man davon aus, dass neue Klassen eher wachsen als neue Methoden, so dass das Hinzufügen weiterer Zeilen sozusagen einfacher ist. Sie fügen einfach eine neue Klasse hinzu, geben an, was in dieser Klasse anders ist, und erben den Rest.

Manchmal sind die Klassen jedoch relativ statisch, aber Sie müssen häufig weitere Methoden hinzufügen, indem Sie Spalten hinzufügen. Der Standardweg in einem OO-Design wäre, solche Methoden zu allen Klassen hinzuzufügen, was kostspielig sein kann. Das Visitor-Muster macht dies einfach.

Das ist übrigens das Problem, das Scalas Musterübereinstimmungen lösen sollen.

23voto

S.Lott Punkte 371691

Les Besucher Entwurfsmuster eignet sich sehr gut für "rekursive" Strukturen wie Verzeichnisbäume, XML-Strukturen oder Dokumentumrisse.

Ein Visitor-Objekt besucht jeden Knoten in der rekursiven Struktur: jedes Verzeichnis, jedes XML-Tag, was auch immer. Das Visitor-Objekt läuft nicht in einer Schleife durch die Struktur. Stattdessen werden die Visitor-Methoden auf jeden Knoten der Struktur angewendet.

Hier ist eine typische rekursive Knotenstruktur. Das kann ein Verzeichnis oder ein XML-Tag sein. [Wenn Sie ein Java-Kenner sind, können Sie sich eine Menge zusätzlicher Methoden zum Aufbau und zur Pflege der Kinderliste vorstellen.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

Les visit Methode wendet ein Visitor-Objekt auf jeden Knoten in der Struktur an. In diesem Fall handelt es sich um einen Top-Down-Besucher. Sie können die Struktur der visit Methode, um eine Bottom-up- oder eine andere Reihenfolge zu erstellen.

Hier ist eine Superklasse für Besucher. Sie wird verwendet von der visit Methode. Sie "erreicht" jeden Knoten in der Struktur. Da die visit Methodenaufrufe up y down kann der Besucher den Überblick über die Tiefe behalten.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

Eine Unterklasse könnte Dinge wie das Zählen von Knoten auf jeder Ebene tun und eine Liste von Knoten akkumulieren, wodurch eine schöne Pfadhierarchie mit Abschnittsnummern entsteht.

Hier ist eine Anwendung. Sie baut eine Baumstruktur auf, someTree . Sie schafft eine Visitor , dumpNodes .

Dann wendet sie die dumpNodes zum Baum. Die dumpNode Objekt wird jeden Knoten im Baum "besuchen".

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

Der TreeNode visit Algorithmus stellt sicher, dass jeder TreeNode als Argument für den Visitor verwendet wird. arrivedAt Methode.

22voto

Oddthinking Punkte 22694

Man kann es auch so sehen, dass das Besuchermuster eine Möglichkeit ist, Ihren Kunden die Möglichkeit zu geben, zusätzliche Methoden zu allen Ihren Klassen in einer bestimmten Klassenhierarchie hinzuzufügen.

Sie ist nützlich, wenn Sie eine relativ stabile Klassenhierarchie haben, aber sich die Anforderungen an diese Hierarchie ändern.

Das klassische Beispiel dafür sind Compiler und dergleichen. Ein abstrakter Syntaxbaum (Abstract Syntax Tree, AST) kann die Struktur der Programmiersprache genau definieren, aber die Operationen, die Sie mit dem AST durchführen wollen, werden sich mit dem Fortschritt Ihres Projekts ändern: Code-Generatoren, Pretty-Printer, Debugger, Komplexitätsmetrik-Analyse.

Ohne das Visitor Pattern müsste ein Entwickler jedes Mal, wenn er ein neues Feature hinzufügen möchte, diese Methode zu jedem Feature der Basisklasse hinzufügen. Dies ist besonders schwierig, wenn die Basisklassen in einer separaten Bibliothek erscheinen oder von einem separaten Team erstellt werden.

(Ich habe gehört, dass das Visitor-Muster im Widerspruch zu guten OO-Praktiken steht, weil es die Datenoperationen von den Daten wegbewegt. Das Visitor-Muster ist genau in der Situation nützlich, in der die normalen OO-Praktiken versagen).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X