2012-04-12 17 views
23

Also habe ich die gesamte Dokumentation über das Besuchermuster gelesen, und ich bin immer noch mächtig verwirrt. Ich habe dieses Beispiel aus einer anderen SO-Frage genommen, könnte mir jemand helfen, das zu verstehen? Wann verwenden wir beispielsweise ein Designmuster für Besucher? Ich denke, ich habe vielleicht etwas davon verstanden, aber ich bin einfach nicht in der Lage, das Gesamtbild zu sehen. Woher weiß ich, wann ich es benutzen kann?Besuchermuster Erklärung

class equipmentVisited 
{ 
    virtual void accept(equipmentVisitor* visitor) = 0; 
} 

class floppyDisk : public equipmentVisited 
{ 
    virtual void accept(equipmentVisitor* visitor); 
} 

class processor : public equipmentVisited 
{ 
    virtual void accept(equipmentVisitor* visitor); 
} 

class computer : public equipmentVisited 
{ 
    virtual void accept(equipmentVisitor* visitor); 
} 

class equipmentVisitor 
{ 
    virtual void visitFloppyDisk(floppyDisk*); 
    virtual void visitProcessor(processor*); 
    virtual void visitComputer(computer*); 
} 

// Some additional classes inheriting from equipmentVisitor would be here 

equipmentVisited* visited; 
equipmentVisitor* visitor; 

// Here you initialise visited and visitor in any convenient way 

visited->accept(visitor); 
+1

für ein vollständiges Beispiel finden Sie in das Java-Beispiel in http://en.wikipedia.org/wiki/Visitor_pattern#Java_example – stefaanv

+0

In diesem Beispiel, das Sie einfach nur equipmentVisitor die Funktion Besuch nutzen machen könnten, mit drei überladenen Funktionen jeder nimmt eine andere abgeleitete equipemtVisited-Klasse als Parameter. Nur ich würde diese Klasse zum Ausrüstungsbesucher machen. –

Antwort

25

Das Besuchermuster wird verwendet, um double dispatch zu implementieren. In einfachen Worten bedeutet dies, dass der Code, der ausgeführt wird, von den Laufzeittypen zweier Objekte abhängt.

Wenn Sie eine reguläre virtuelle Funktion aufrufen, ist es ein einzelner Versand: Der Code, der ausgeführt wird, hängt vom Laufzeittyp eines einzelnen Objekts ab, nämlich demjenigen, dessen virtuelle Methode Sie aufrufen.

Mit dem Besuchermuster, die Methode, die von der Art der beiden Objekte hängt letztlich davon genannt wird - die auf dem die equipmentVisitor, und die Art des Objekttyps des Objekts Sie accept nennen Implementierung (dh die equipmentVisited Unterklasse) .

Es gibt andere Möglichkeiten, die doppelte Verteilung in C++ zu implementieren. Punkt 31 von Scott Meyers "More Effective C++" behandelt dieses Thema in der Tiefe.

+1

So kann es wie jede Unterklasse von equipmentVisited die Funktion 'accept' von einer der Unterklassen von EquipmentVistor nennen? Ist das korrekt? –

+0

@dasblinkenlight - Ich habe das Besuchermuster nie "bekommen"! Kämpfe immer noch damit. Also, antworten Sie bitte auf diese Frage: Ich habe eine Master-Klasse, die von einer Client-Klasse erbt. Auf diese Weise kann der Meister alles tun, was der Kunde tun kann, und noch ein paar mehr. Wenn ich diese Klassen polymorph verwenden möchte, brauche ich eine generische Basisklasse. PROBLEM: Jetzt erbt meine Master-Klasse von zwei Klassen. Denkst du, dass das Besuchermuster mein Problem lösen könnte? – Patricia

+0

@Patricia Warum benötigen Sie eine generische Basisklasse? Warum können Sie nicht einfach 'Client' als Basiszeiger/Referenztyp verwenden? Polymorphismus funktioniert mit jeder Art von Vererbung, nicht nur mit dem eingeschränkten "Interface" -Muster. "Master" erbt von "Client", so dass ein Zeiger oder eine Referenz auf "Client" verwendet werden kann, um "virtuelle" Methoden auf einem "Master" aufzurufen. Zu denken, dass Sie eine weitere Basisklasse in die Mischung hinzufügen müssen, ist falsch und ein völlig überflüssiger Treffer zur Effizienz. Das ist das Problem. –

13

Ich denke, der Name des Musters Besucher ist ziemlich unglücklich. Anstelle des Wortes Besucher würde ich Functor oder Operator sagen und statt "besuchen" würde ich "anwenden" sagen.

Mein Verständnis von Besuchermuster ist wie folgt:

In Vorlage Meta-Programmierung (STL/BOOST) (Kompilierung Bindung) Sie können (das orthogonale Design) die Trennungen von Operationen von Strukturen erreichen, durch die mittels Funktionsobjekte (Funktoren.) zum Beispiel in

template <class RandomAccessIterator, class Compare> 
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp); 

ist die comp einen Funktor/Operator ‚kleiner als‘ Betrieb in einem sehr allgemeiner Weise darstellt, so dass Sie viele nicht-Varianten Art haben müssen Funktion:

Für Besuchermuster möchten Sie etwas Ähnliches erreichen, aber im Falle der Laufzeit (spät) Bindung:

Sie möchten die Schnittstelle von A vereinfachen, Sie wollen die Möglichkeit für zukünftige Erweiterungen (neue Operationen arbeiten mit A) und Sie wollen die Stabilität der Schnittstelle von A bei diesen Erweiterungen erreichen.

Von der ursprünglichen ‚Fett‘ Klasse:

class A 
{ 
    public: 
    virtual void function_or_operation_1();//this can be implemented in terms of public interface of the other functions 
    virtual void function_or_operation_2(); 
    //..etc 

    virtual void function_or_operation_N(); 
    public: 
    //stable public interface, some functions of procedures 

    private: 
    //.... 
} 

Sie entfernen so viel Funktion von öffentlicher Schnittstelle wie möglich (solange sie im Hinblick auf den nicht-extrahierten Funktionen derselben öffentlichen Schnittstelle implementiert werden kann) und die Operationen wie die Funktors Objekte oder Objekte aus einer neuen functor Hierarchie darstellen:

Sie die Anzahl der Funktion in der Basisklasse reduzieren, indem er sehr generische Schnittstelle mit nach vorn erklärt Functor_or_Operator:

class Functor_or_Operator; 
class A 
{ 
    public: 
    virtual void apply(Functor_or_Operator*);//some generic function operates on this objects from A hierarchy 
    //..etc 
    public: 
    //stable public interface, some functions 

    private: 
    //.... 
} 

// Nun haben Sie N (= 3) Klassen in A-Hierarchie (A, B, C) und M-Operationen oder Funktionen repräsentiert durch Klassen in Functor_oder_Operator-Hierarchie Sie müssen N * M-Definitionen implementieren, wie jeder Operation von Functor_or_Operator funktioniert für jede Klasse in A-Hierarchie. Die große Sache ist, Sie können es tun, ohne die Schnittstelle der Klasse 'A' zu ändern. Die Deklaration der Klasse 'A' wird sehr stabil bei den neuen Hinzufügungen beim Einfügen neuer Operationen oder Funktionen, die mit Objekten der A-Hierarchie arbeiten oder bei neuen abgeleiteten Klassen in der A-Hierarchie. Die Stabilität von A (keine Änderungen an A) in Gegenwart von Hinzufügungen ist wichtig, um eine kostspielige (und manchmal unmögliche) Neukompilierung von Software zu vermeiden, die an vielen Stellen Header von A enthält.

Für jede neue Klasse in der A-Hierarchie erweitern Sie die Definition der Basis Functor_or_Operator, Sie fügen neue Implementierungsdateien hinzu, aber Sie müssen nie die Kopfzeile der Basisklasse A (normalerweise Schnittstelle oder abstrakte Klasse) berühren.

class Functor_or_Operator 
    { 
    virtual void apply(A*)=0; 
    virtual void apply(B*)=0; 
    virtual void apply(C*)=0; 
    } 

    void A::apply(Functor_or_Operator* f) 
    { f->apply(this);} //you need this only if A is not abstract (it is instantiable) 

    class B:public A 
    { 
    public: 
    void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymhorphic Functor f on this object 
    //..the rest of B implementation. 
    } 

    class C:public A 
    { 
    public: 
    void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymorfic Functor f on this object 
    //..the rest of C implementation. 
    } 

    class Functor_or_Operator_1:public Functor_or_Operator 
    { 
    public: 
     //implementations of application of a function represented by Functor_or_Operator_1 on each A,B,C 
     void apply(A*) {}//(only if A is instantiable,not an abstract class) 
     void apply(B*) {} 
     void apply(C*) {} 
    } 

    class Functor_or_Operator_2:public Functor_or_Operator 
    { 
    public: 
     //implementations of application of a function represented by Functor_or_Operator_2 on each A,B,C 
     void apply(A*) {}//(only if A is instantiable,not an abstract class) 
     void apply(B*) {} 
     void apply(C*) {} 
    }