it-swarm.com.de

Wann sollte ich das Besucherdesignmuster verwenden?

Ich sehe immer wieder Hinweise auf das Besuchermuster in Blogs, aber ich muss zugeben, ich verstehe es einfach nicht. Ich habe den Wikipedia-Artikel für das Muster gelesen und verstehe seine Mechanik, bin aber immer noch verwirrt, wann ich es verwenden würde.

Als jemand, der gerade erst das Dekorationsmuster wirklich bekommen hat und es jetzt überall benutzt, möchte ich dieses scheinbar handliche Muster auch wirklich intuitiv verstehen können.

282
George Mauer

Ich bin mit dem Besuchermuster nicht sehr vertraut. 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 {  };

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

Jetzt möchten wir der Hierarchie eine neue Operation hinzufügen, nämlich dass jedes Tier seinen Sound erzeugen soll. Soweit die Hierarchie so einfach ist, können Sie es mit geradlinigem Polymorphismus tun:

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"; }

Auf diese Weise müssen Sie jedoch jedes Mal, wenn Sie einen Vorgang hinzufügen möchten, die Schnittstelle für jede einzelne Klasse der Hierarchie ändern. Nehmen Sie nun an, Sie sind mit der ursprünglichen Benutzeroberfläche zufrieden und möchten möglichst wenige Änderungen daran vornehmen.

Mit dem Besuchermuster können Sie jeden neuen Vorgang in eine geeignete Klasse verschieben. Die Hierarchie muss nur einmal erweitert werden. Machen wir das. Zuerst definieren wir eine abstrakte Operation (die "Visitor" -Klasse 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 implementieren wir die eigentliche Operation, ohne weder Cat noch Dog 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 eine Möglichkeit, Operationen hinzuzufügen, ohne die Hierarchie mehr zu ändern. __ So funktioniert es:

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

Der Grund für Ihre Verwirrung liegt wahrscheinlich darin, dass der Besucher eine verhängnisvolle Verwechslung ist. Viele (prominente1!) Programmierer sind über dieses Problem gestolpert. Tatsächlich implementiert es double dispatching in Sprachen, die es nicht nativ unterstützen (die meisten tun dies nicht).


1) Mein Lieblingsbeispiel ist Scott Meyers, gefeierter Autor von "Effective C++", der dieses seiner wichtigsten C++ - Aha! Momente je .

118
Konrad Rudolph

Jeder hier ist korrekt, aber ich denke, dass er das "Wann" nicht anspricht. Zuerst von Design Patterns:

In Visitor können Sie ein neues .__ definieren. Betrieb ohne Änderung der Klassen der Elemente, auf die es wirkt.

Denken wir jetzt an eine einfache Klassenhierarchie. Ich habe die Klassen 1, 2, 3 und 4 und die Methoden A, B, C und D. Legen Sie sie wie in einer Tabelle zusammen: Die Klassen sind Zeilen und die Methoden Spalten.

Beim objektorientierten Design wird davon ausgegangen, dass Sie mit größerer Wahrscheinlichkeit neue Klassen als neue Methoden entwickeln, sodass das Hinzufügen von mehr 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 - Spalten hinzufügen. Die Standardmethode in einem OO - Design wäre das Hinzufügen solcher Methoden zu allen Klassen, was kostspielig sein kann. Das Besuchermuster macht dies einfach.

Übrigens ist dies das Problem, das Scalas Mustermatches lösen wollen.

76

Das Visitor - Designmuster funktioniert 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 und was auch immer. Das Besucherobjekt durchläuft die Struktur nicht. Stattdessen werden Besuchermethoden auf jeden Knoten der Struktur angewendet. 

Hier ist eine typische rekursive Knotenstruktur. Könnte ein Verzeichnis oder ein XML-Tag sein. [Wenn Sie ein Java-Mitarbeiter sind, stellen Sie sich eine Menge zusätzlicher Methoden vor, um die Kinderliste zu erstellen und zu verwalten.]

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()

Die visit-Methode wendet ein Visitor-Objekt auf jeden Knoten in der Struktur an. In diesem Fall ist es ein Top-Down-Besucher. Sie können die Struktur der visit-Methode ändern, um Bottom-Up oder eine andere Reihenfolge zu verwenden.

Hier ist eine Superklasse für Besucher. Es wird von der visit-Methode verwendet. Es "kommt" bei jedem Knoten in der Struktur an. Da die visit-Methode up und down aufruft, kann der Besucher die Tiefe verfolgen.

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 kann zum Beispiel Knoten zählen auf jeder Ebene und eine Liste von Knoten akkumulieren, wobei eine hierarchische Abschnittsnummer für einen Nizza-Pfad generiert wird.

Hier ist eine Anwendung. Es baut eine Baumstruktur auf, someTree. Es erstellt eine Visitor, dumpNodes

Dann wendet er die dumpNodes auf den Baum an. Das dumpNode-Objekt "besucht" jeden Knoten in der Baumstruktur.

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 die arrivedAt-Methode des Besuchers verwendet wird.

20
S.Lott

Eine Möglichkeit, dies zu betrachten, besteht darin, dass das Besuchermuster eine Möglichkeit ist, Ihren Kunden das Hinzufügen zusätzlicher Methoden zu allen Ihren Klassen in einer bestimmten Klassenhierarchie zu ermöglichen.

Dies ist nützlich, wenn Sie über eine relativ stabile Klassenhierarchie verfügen, sich aber ändernde Anforderungen hinsichtlich dessen, was mit dieser Hierarchie zu tun ist.

Das klassische Beispiel ist für Compiler und dergleichen. Ein Abstract Syntax Tree (AST) kann die Struktur der Programmiersprache genau definieren, aber die Vorgänge, die Sie an AST ausführen möchten, ändern sich mit dem Fortschreiten Ihres Projekts: Code-Generatoren, Pretty-Printers, Debugger Analyse der Komplexitätsmetrik.

Ohne das Besuchermuster musste ein Entwickler jedes Mal, wenn er ein neues Feature hinzufügen wollte, diese Methode zu jedem Feature in 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 Besuchermuster in Konflikt mit guten OO - Praktiken steht, da es die Operationen der Daten von den Daten wegschiebt. Das Besuchermuster ist in genau der Situation nützlich, in der die normalen OO Praktiken schlagen fehl.)

18
Oddthinking

Es gibt mindestens drei sehr gute Gründe für die Verwendung des Besuchermusters:

  1. Reduzieren Sie die Verbreitung von Code, der sich bei Änderungen der Datenstrukturen nur geringfügig unterscheidet.

  2. Wenden Sie dieselbe Berechnung auf mehrere Datenstrukturen an, ohne den Code zu ändern, der die Berechnung implementiert.

  3. Fügen Sie Informationen zu älteren Bibliotheken hinzu, ohne den alten Code zu ändern.

Bitte schau nach einen Artikel, den ich darüber geschrieben habe .

14
Richard Gomes

Wie Konrad Rudolph bereits betont hat, ist es für Fälle geeignet, in denen wir doppelten Versand benötigen

Hier ist ein Beispiel, um eine Situation zu zeigen, in der wir doppelten Versand benötigen und wie der Besucher uns dabei hilft.

Beispiel:

Nehmen wir an, ich habe 3 Arten von Mobilgeräten - iPhone, Android, Windows Mobile.

In all diesen drei Geräten ist ein Bluetooth-Funkgerät installiert.

Nehmen wir an, dass das Bluetooth-Radio von zwei verschiedenen OEMs stammen kann - Intel & Broadcom.

Um das Beispiel für unsere Diskussion relevant zu machen, nehmen wir an, dass sich die APIs von Intel Radio von denen von Broadcom Radio unterscheiden.

So sieht mein Unterricht aus -

enter image description hereenter image description here

Jetzt möchte ich eine Operation vorstellen - Einschalten des Bluetooths auf einem mobilen Gerät.

Die Funktionssignatur sollte ungefähr so ​​aussehen:

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

Also abhängig von Richtiger Gerätetyp und Abhängig vom richtigen Bluetooth-Funktyp kann dieser durch Aufrufen geeigneter Schritte oder Algorithmen .

Im Prinzip wird es zu einer 3 x 2-Matrix, in der ich versuche, die richtige Operation in Abhängigkeit von der richtigen Art der beteiligten Objekte zu vektorisieren.

Ein polymorphes Verhalten, das von der Art der beiden Argumente abhängt.

enter image description here

Jetzt kann das Besuchermuster auf dieses Problem angewendet werden. Inspiration kommt von der Wikipedia-Seite, auf der Folgendes angegeben ist: - “Der Besucher kann einer Klassenfamilie im Wesentlichen neue virtuelle Funktionen hinzufügen, ohne die Klassen selbst zu ändern. Stattdessen wird eine Besucherklasse erstellt, die alle entsprechenden Spezialisierungen der virtuellen Funktion implementiert. Der Besucher nimmt die Instanzreferenz als Eingabe und setzt das Ziel durch doppelten Versand um. ”

Aufgrund der 3x2-Matrix ist hier ein doppelter Versand erforderlich

So wird das Setup aussehen - enter image description here

Ich habe das Beispiel geschrieben, um eine andere Frage zu beantworten. Der Code und seine Erklärung werden erwähnt hier .

13
Kapoor

Ich habe es in folgenden Links leichter gefunden:

In http://www.remondo.net/visitor-pattern-example-csharp/ Ich habe ein Beispiel gefunden, das ein Mock-Beispiel zeigt, das den Nutzen des Besuchermusters zeigt. Hier haben Sie verschiedene Containerklassen für Pill:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

Wie Sie oben sehen, enthalten Sie BilsterPack Paare von Pillen, so dass Sie die Anzahl der Paare mit 2 multiplizieren müssen. Außerdem können Sie feststellen, dass Bottleunit verwendet, das einen anderen Datentyp hat und umgewandelt werden muss.

In der Hauptmethode können Sie die Pillenzahl also mit folgendem Code berechnen:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

Beachten Sie, dass der obige Code Single Responsibility Principle verletzt. Das bedeutet, dass Sie den Hauptmethodencode ändern müssen, wenn Sie einen neuen Containertyp hinzufügen. Auch das Wechseln länger ist schlechte Praxis. 

Also, indem Sie folgenden Code einführen:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Sie haben die Verantwortung für das Zählen der Anzahl von Pills auf die Klasse mit dem Namen PillCountVisitor verschoben (und die switch case-Anweisung wurde entfernt). Das bedeutet, wann immer Sie einen neuen Typ eines Pillenbehälters hinzufügen müssen, sollten Sie nur die PillCountVisitor-Klasse ändern. Beachten Sie auch, dass die IVisitor-Schnittstelle in anderen Szenarien verwendet werden kann.

Durch Hinzufügen der Accept-Methode zur Pill-Container-Klasse:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

wir gestatten Besuchern, Pill-Container-Klassen zu besuchen.

Am Ende berechnen wir die Pillenzahl mit folgendem Code:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Das bedeutet: Jeder Pillenbehälter erlaubt dem PillCountVisitor Besucher, seine Pillen zählen zu sehen. Er weiß, wie man die Pille zählt.

Bei visitor.Count ist der Wert von Pillen.

In http://butunclebob.com/ArticleS.UncleBob.IuseVisitor Sie sehen ein reales Szenario, in dem Sie Polymorphismus (die Antwort) nicht verwenden können, um das Einzelverantwortungsprinzip zu befolgen. In der Tat in:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

die reportQtdHoursAndPay-Methode dient zur Berichterstattung und Darstellung und verstößt gegen das Prinzip der Einzelverantwortung. Daher ist es besser, das Besuchermuster zu verwenden, um das Problem zu überwinden.

Doppelversand ist nur ein Grund unter anderem, dieses Muster zu verwenden.
Beachten Sie jedoch, dass es die einzige Möglichkeit ist, doppelten oder mehr Versand in Sprachen zu implementieren, die ein einzelnes Versandparadigma verwenden.

Hier sind Gründe, das Muster zu verwenden:

1) Wir möchten neue Operationen definieren, ohne das Modell jedes Mal zu ändern weil sich das Modell nicht oft ändert, während sich Operationen häufig ändern.

2) Wir wollen Modell und Verhalten nicht koppeln weil wir wollen ein wiederverwendbares Modell haben in mehreren Anwendungen oder wir wollen haben ein erweiterbares Modell, mit dem Client-Klassen ihr Verhalten mit eigenen Klassen definieren können.

3) Wir haben gemeinsame Operationen, die vom konkreten Typ des Modells abhängen, aber wir möchten die Logik nicht in jeder Unterklasse implementieren, da dies die gemeinsame Logik in mehreren Klassen und damit an mehreren Stellen auflösen würde =.

4) Wir verwenden ein Domänenmodell-Design und Modellklassen derselben Hierarchie führen zu viele verschiedene Dinge aus, die woanders gesammelt werden könnten.

5) Wir brauchen einen doppelten Versand.
Wir haben Variablen, die mit Schnittstellentypen deklariert sind, und wir möchten sie entsprechend ihrem Laufzeittyp verarbeiten können ... natürlich ohne Verwendung von if (myObj instanceof Foo) {} oder eines Tricks.
Die Idee ist zum Beispiel, diese Variablen an Methoden zu übergeben, die einen konkreten Typ der Schnittstelle als Parameter deklarieren, um eine bestimmte Verarbeitung anzuwenden. Diese Vorgehensweise ist bei Sprachen nicht sofort möglich, da der zur Laufzeit aufgerufene Wert nur vom Laufzeittyp des Empfängers abhängt.
Beachten Sie, dass in Java die aufzurufende Methode (Signatur) zur Kompilierungszeit ausgewählt wird und vom deklarierten Typ der Parameter und nicht vom Laufzeittyp abhängt.

Der letzte Punkt, der ein Grund für die Verwendung des Besuchers ist, ist auch eine Konsequenz, da Sie beim Implementieren des Besuchers (natürlich für Sprachen, die keinen Mehrfachversand unterstützen) unbedingt eine Doppelversandimplementierung einführen müssen.

Beachten Sie, dass das Durchlaufen von Elementen (Iteration), um den Besucher auf jedes einzelne anzuwenden, kein Grund ist, das Muster zu verwenden.
Sie verwenden das Muster, weil Sie Modell und Verarbeitung aufteilen.
Und wenn Sie das Muster verwenden, profitieren Sie zusätzlich von einer Iterator-Fähigkeit.
Diese Fähigkeit ist sehr mächtig und geht über die Iteration bei gebräuchlichen Typen mit einer bestimmten Methode hinaus, da accept() eine generische Methode ist.
Es ist ein spezieller Anwendungsfall. Also werde ich das beiseite legen.


Beispiel in Java

Ich werde den Mehrwert des Musters anhand eines Schachbeispiels veranschaulichen, in dem wir die Verarbeitung definieren möchten, wenn der Spieler eine bewegte Figur anfordert.

Ohne die Verwendung des Besuchermusters könnten wir das Bewegungsverhalten von Teilen direkt in den Teil-Unterklassen definieren.
Wir könnten zum Beispiel eine Piece Schnittstelle haben wie:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

Jede Piece-Unterklasse würde dies wie folgt implementieren:

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

Und das Gleiche für alle Piece-Unterklassen.
Hier ist eine Diagrammklasse, die diesen Entwurf veranschaulicht:

[model class diagram

Dieser Ansatz weist drei wichtige Nachteile auf:

- Verhaltensweisen wie performMove() oder computeIfKingCheck() verwenden sehr wahrscheinlich gemeinsame Logik.
Unabhängig vom konkreten Piece setzt performMove() die aktuelle Figur an einen bestimmten Ort und nimmt möglicherweise die gegnerische Figur.
Aufteilen verwandter Verhaltensweisen in mehrere Klassen, anstatt sie zu sammeln. Die Wartbarkeit wird erschwert.

- Die Verarbeitung als checkMoveValidity() sollte nichts sein, was die Unterklassen Piece möglicherweise sehen oder ändern.
Es ist ein Scheck, der über menschliche oder Computeraktionen hinausgeht. Diese Prüfung wird bei jeder von einem Spieler angeforderten Aktion durchgeführt, um sicherzustellen, dass der angeforderte Spielzug gültig ist.
Deshalb möchten wir das nicht einmal in der Piece -Oberfläche bereitstellen.

- In Schachspielen, die für Bot-Entwickler eine Herausforderung darstellen, bietet die Anwendung im Allgemeinen eine Standard-API (Piece Schnittstellen, Unterklassen, Board, allgemeines Verhalten usw.) und lässt Entwickler ihre Bot-Strategie erweitern.
Dazu müssen wir ein Modell vorschlagen, bei dem Daten und Verhalten in den Implementierungen von Piece nicht eng miteinander gekoppelt sind.

Verwenden wir also das Besuchermuster!

Wir haben zwei Arten von Strukturen:

- die Modellklassen, die es akzeptieren, besucht zu werden (die Stücke)

- die Besucher, die sie besuchen (Umzugsvorgänge)

Hier ist ein Klassendiagramm, das das Muster veranschaulicht:

enter image description here

Im oberen Teil haben wir die Besucher und im unteren Teil haben wir die Modellklassen.

Hier ist die Schnittstelle PieceMovingVisitor (für jede Art von Piece angegebenes Verhalten):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

Das Stück ist jetzt definiert:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

Ihre Schlüsselmethode ist:

void accept(PieceMovingVisitor pieceVisitor);

Es liefert den ersten Versand: einen Aufruf basierend auf dem Piece Empfänger.
Zur Kompilierungszeit ist die Methode an die accept() -Methode der Piece-Schnittstelle gebunden, und zur Laufzeit wird die begrenzte Methode für die Laufzeitklasse Piece aufgerufen.
Und es ist die Implementierung der Methode accept(), die einen zweiten Versand durchführt.

In der Tat ruft jede Piece -Unterklasse, die von einem PieceMovingVisitor -Objekt besucht werden soll, die PieceMovingVisitor.visit() -Methode auf, indem sie selbst als Argument übergibt.
Auf diese Weise begrenzt der Compiler mit der Kompilierzeit den Typ des deklarierten Parameters mit dem konkreten Typ.
Da ist der zweite Versand.
Hier ist die Unterklasse Bishop, die Folgendes veranschaulicht:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

Und hier ein Anwendungsbeispiel:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

Nachteile für Besucher

Das Besuchermuster ist ein sehr leistungsfähiges Muster, weist jedoch auch einige wichtige Einschränkungen auf, die Sie berücksichtigen sollten, bevor Sie es verwenden.

1) Gefahr des Reduzierens/Brechens der Kapselung

In einigen Betriebsarten kann das Besuchermuster die Kapselung von Domänenobjekten verringern oder aufheben.

Da die Klasse MovePerformingVisitor die Koordinaten des tatsächlichen Teils festlegen muss, muss die Schnittstelle Piece eine Möglichkeit bieten, dies zu tun:

void setCoordinates(Coordinates coordinates);

Die Verantwortung für die Änderung von Piece Koordinaten steht jetzt anderen Klassen als Piece Unterklassen offen.
Das Verschieben der vom Besucher durchgeführten Verarbeitung in die Unterklassen Piece ist ebenfalls keine Option.
Es wird in der Tat ein weiteres Problem verursachen, da Piece.accept() jede Besucherimplementierung akzeptiert. Es weiß nicht, was der Besucher ausführt, und weiß daher nicht, ob und wie der Status des Elements geändert werden soll.
Eine Möglichkeit, den Besucher zu identifizieren, besteht darin, eine Nachbearbeitung in Piece.accept() entsprechend der Besucherimplementierung durchzuführen. Es wäre eine sehr schlechte Idee, da dies eine hohe Kopplung zwischen Visitor-Implementierungen und Piece-Unterklassen erzeugen würde und außerdem wahrscheinlich die Verwendung eines Tricks als getClass(), instanceof oder eines Markers, der den Visitor identifiziert, erforderlich wäre Implementierung.

2) Modellwechsel erforderlich

Im Gegensatz zu einigen anderen Verhaltensmustern wie beispielsweise Decorator ist das Besuchermuster aufdringlich.
Wir müssen in der Tat die anfängliche Empfängerklasse ändern, um eine accept() -Methode bereitzustellen, die akzeptiert, dass sie besucht wird.
Wir hatten kein Problem mit Piece und seinen Unterklassen, da dies unsere Klassen sind.
In integrierten Klassen oder Klassen von Drittanbietern sind die Dinge nicht so einfach.
Wir müssen sie umbrechen oder erben (wenn wir können), um die accept() -Methode hinzuzufügen.

3) Indirektionen

Das Muster erzeugt mehrere Indirektionen.
Der doppelte Versand bedeutet zwei Aufrufe anstelle eines einzigen:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

Außerdem können zusätzliche Indirektionen auftreten, wenn der Besucher den Status des besuchten Objekts ändert.
Es kann wie ein Zyklus aussehen:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)
5
davidxxx

Cay Horstmann hat ein großartiges Beispiel dafür, wo man sich bewerben kann Besucher in seinem OO Design- und Musterbuch . Er fasst das Problem zusammen:

Zusammengesetzte Objekte haben oft eine komplexe Struktur, die sich aus einzelnen Elementen zusammensetzt. Einige Elemente können wieder untergeordnete Elemente haben. ... Eine Operation für ein Element besucht seine untergeordneten Elemente, wendet die Operation auf sie an und kombiniert die Ergebnisse. ... Es ist jedoch nicht einfach, einem solchen Entwurf neue Operationen hinzuzufügen.

Der Grund, warum es nicht einfach ist, ist, dass Operationen innerhalb der Strukturklassen selbst hinzugefügt werden. Angenommen, Sie haben ein Dateisystem:

FileSystem class diagram

Hier sind einige Operationen (Funktionalitäten), die wir möglicherweise mit dieser Struktur implementieren möchten:

  • Anzeigen der Namen der Knotenelemente (eine Dateiliste)
  • Zeigt die berechnete Größe der Knotenelemente an (wobei die Größe eines Verzeichnisses die Größe aller seiner untergeordneten Elemente enthält).
  • usw.

Sie können jeder Klasse im Dateisystem Funktionen hinzufügen, um die Operationen zu implementieren (und dies haben die Benutzer in der Vergangenheit getan, da es sehr offensichtlich ist, wie dies getan wird). Das Problem besteht darin, dass Sie bei jedem Hinzufügen einer neuen Funktionalität (die Zeile "etc." oben) möglicherweise den Strukturklassen immer mehr Methoden hinzufügen müssen. Irgendwann, nach einer Reihe von Operationen, die Sie zu Ihrer Software hinzugefügt haben, sind die Methoden in diesen Klassen im Hinblick auf den funktionalen Zusammenhalt der Klassen nicht mehr sinnvoll. Beispielsweise haben Sie ein FileNode mit einer Methode calculateFileColorForFunctionABC(), um die neuesten Visualisierungsfunktionen im Dateisystem zu implementieren.

Das Besuchermuster (wie viele Designmuster) entstand aus dem Schmerz und Leiden von Entwicklern, die wussten, dass es eine bessere Möglichkeit gibt, ihren Code ohne Änderungen zuzulassen Dies erfordert überall viele Änderungen und die Einhaltung guter Konstruktionsprinzipien (hohe Kohäsion, geringe Kopplung). Ich bin der Meinung, dass es schwierig ist, die Nützlichkeit vieler Muster zu verstehen, bis Sie diesen Schmerz gespürt haben. Das Erklären des Schmerzes (wie wir es oben mit den hinzugefügten "etc." - Funktionalitäten versuchen) nimmt in der Erklärung Platz ein und ist eine Ablenkung. Das Verstehen von Mustern ist aus diesem Grund schwierig.

Der Besucher ermöglicht es uns, die Funktionalitäten in der Datenstruktur (z. B. FileSystemNodes) von den Datenstrukturen selbst zu entkoppeln. Das Muster ermöglicht es dem Design, die Kohäsion zu berücksichtigen - Datenstrukturklassen sind einfacher (sie haben weniger Methoden) und auch die Funktionalitäten sind in Visitor -Implementierungen eingekapselt. Dies geschieht durch Double-Dispatching (das ist der komplizierte Teil des Musters): Verwenden von accept() -Methoden in den Strukturklassen und visitX() Methoden in den Visitor (der Funktionalität) Klassen:

FileSystem class diagram with Visitor applied

Diese Struktur ermöglicht es uns, neue Funktionalitäten hinzuzufügen, die als konkrete Besucher an der Struktur arbeiten (ohne die Strukturklassen zu ändern).

FileSystem class diagram with Visitor applied

Zum Beispiel ein PrintNameVisitor, das die Verzeichnislistenfunktion implementiert, und ein PrintSizeVisitor, das die Version mit der Größe implementiert. Wir könnten uns vorstellen, eines Tages einen "ExportXMLVisitor" zu haben, der die Daten in XML generiert, oder einen anderen Besucher, der sie in JSON generiert usw. Wir könnten sogar einen Besucher haben, der meinen Verzeichnisbaum mit einem grafische Sprache wie DOT) anzeigt , um mit einem anderen Programm visualisiert zu werden.

Als letzte Anmerkung: Die Komplexität des Besuchers mit seinem Doppelversand macht es schwieriger zu verstehen, zu codieren und zu debuggen. Kurz gesagt, es hat einen hohen Aussenseiterfaktor und steht im Widerspruch zum KISS Prinzip. In einer Umfrage, die von Forschern durchgeführt wurde, zeigte sich, dass der Besucher ein umstrittenes Muster hatte (es gab kein Konsens über seine Nützlichkeit.) Einige Experimente haben sogar gezeigt, dass es den Code nicht einfacher zu warten macht.

5
Fuhrmanator

Meines Erachtens ist der Arbeitsaufwand für das Hinzufügen einer neuen Operation mehr oder weniger gleich, wenn Sie Visitor Pattern oder eine direkte Änderung jeder Elementstruktur verwenden. Wenn ich eine neue Elementklasse hinzufügen würde, beispielsweise Cow, ist die Operation-Schnittstelle betroffen, und diese wird auf alle vorhandenen Elementklassen übertragen, sodass alle Elementklassen neu kompiliert werden müssen. Was ist der Punkt?

5
kaosad

Besuchermuster als gleiche unterirdische Implementierung für die Programmierung von Aspektobjekten. 

Zum Beispiel, wenn Sie eine neue Operation definieren, ohne die Klassen der Elemente zu ändern, für die sie ausgeführt wird 

4
mixturez

Kurze Beschreibung des Besuchermusters. Die Klassen, die geändert werden müssen, müssen die 'Accept'-Methode implementieren. Clients nennen diese Accept-Methode, um einige neue Aktionen für diese Klassenfamilie auszuführen und somit ihre Funktionalität zu erweitern. Kunden können mit dieser akzeptierten Methode eine Vielzahl neuer Aktionen ausführen, indem sie für jede bestimmte Aktion eine andere Besucherklasse übergeben. Eine Besucherklasse enthält mehrere überschriebene Besuchsmethoden, die definieren, wie dieselbe spezifische Aktion für jede Klasse innerhalb der Familie erreicht wird. Diese Besuchsmethoden erhalten eine Instanz, an der sie arbeiten können.

Wenn Sie darüber nachdenken könnten,

  1. Wenn Sie eine Klassenfamilie haben, wissen Sie, dass Sie viele neue Aktionen hinzufügen müssen, aber aus irgendeinem Grund können Sie die Klassenfamilie in der Zukunft nicht ändern oder neu kompilieren.
  2. Wenn Sie eine neue Aktion hinzufügen möchten und diese neue Aktion vollständig innerhalb einer Klasse definiert ist, ist die Besucherklasse nicht auf mehrere Klassen verteilt.
  3. Wenn Ihr Chef sagt, Sie müssen eine Reihe von Klassen erstellen, die gerade tun müssen! ... aber eigentlich weiß niemand genau, was das ist.
4
andrew pate

Besucher

Mit Visitor können Sie einer Klassenfamilie neue virtuelle Funktionen hinzufügen, ohne die Klassen selbst zu ändern. Stattdessen wird eine Besucherklasse erstellt, die alle entsprechenden Spezialisierungen der virtuellen Funktion implementiert

Besucherstruktur:

enter image description here

Besuchermuster verwenden, wenn:

  1. Ähnliche Operationen müssen durchgeführt werden für Objekte verschiedener Typen, die in einer Struktur gruppiert sind
  2. Sie müssen viele verschiedene und nicht verwandte Vorgänge ausführen. Es trennt die Operation von der Objektstruktur
  3. Neue Operationen müssen ohne Änderung der Objektstruktur hinzugefügt werden
  4. Sammeln Sie verwandte Operationen in einer einzelnen Klasse anstatt Sie zu zwingen, Klassen zu ändern oder abzuleiten
  5. Fügen Sie Funktionen zu Klassenbibliotheken hinzu, für die Sie entweder die Quelle nicht haben oder die Quelle nicht ändern können

Obwohl das Muster Visitor die Flexibilität bietet, neue Operationen hinzuzufügen, ohne den vorhandenen Code in Object zu ändern, ist diese Flexibilität mit einem Nachteil verbunden.

Wenn ein neues Visitable-Objekt hinzugefügt wurde, sind Codeänderungen in den Visitor & ConcreteVisitor-Klassen erforderlich. Es gibt eine Problemumgehung, um dieses Problem zu beheben: Verwenden Sie Reflection, was sich auf die Leistung auswirkt.

Code-Auszug:

import Java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Erläuterung:

  1. Visitable (Element) ist eine Schnittstelle, und diese Schnittstellenmethode muss zu einer Reihe von Klassen hinzugefügt werden.
  2. Visitor ist eine Schnittstelle, die Methoden zum Ausführen einer Operation an Visitable Elementen enthält.
  3. GameVisitor ist eine Klasse, die die Schnittstelle Visitor (ConcreteVisitor) implementiert.
  4. Jedes Visitable Element akzeptiert Visitor und ruft eine relevante Methode der Visitor Schnittstelle auf.
  5. Sie können Game als Element und konkrete Spiele wie Chess,Checkers and Ludo Als ConcreteElements behandeln.

Im obigen Beispiel sind Chess, Checkers and Ludo Drei verschiedene Spiele (und Visitable Klassen). An einem schönen Tag bin ich auf ein Szenario gestoßen, in dem Statistiken zu jedem Spiel aufgezeichnet wurden. Ohne die einzelnen Klassen zu ändern, um Statistikfunktionen zu implementieren, können Sie diese Verantwortung in der Klasse GameVisitor zentralisieren. Dies erledigt den Trick für Sie, ohne die Struktur jedes Spiels zu ändern.

ausgabe:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Beziehen auf

oodesign Artikel

Sourcing Artikel

für mehr Details

Dekorateur

muster ermöglicht das Hinzufügen von Verhalten zu einem einzelnen Objekt, entweder statisch oder dynamisch, ohne das Verhalten anderer Objekte derselben Klasse zu beeinflussen

Zusammenhängende Posts:

Decorator Pattern für IO

Wann wird das Dekorationsmuster verwendet?

3
Ravindra babu

Basierend auf der hervorragenden Antwort von @Federico A. Ramponi.

Stellen Sie sich vor, Sie haben diese Hierarchie:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

Was passiert, wenn Sie hier eine "Walk" -Methode hinzufügen müssen? Das wird für das gesamte Design schmerzhaft sein.

Gleichzeitig werden durch das Hinzufügen der "Walk" -Methode neue Fragen generiert. Was ist mit "Essen" oder "Schlaf"? Müssen wir der Animal-Hierarchie wirklich eine neue Methode für jede neue Aktion oder Operation hinzufügen, die wir hinzufügen möchten? Das ist hässlich und am wichtigsten, wir werden niemals die Animal-Schnittstelle schließen können. Mit dem Besuchermuster können wir der Hierarchie eine neue Methode hinzufügen, ohne die Hierarchie zu ändern!

Überprüfen Sie also dieses C # -Beispiel:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
3
Tomás Escamez

Mir gefällt die Beschreibung und das Beispiel aus http://python-3-patterns-idioms-test.readthedocs.io/de/latest/Visitor.html .

Die Annahme ist, dass Sie über eine primäre Klassenhierarchie verfügen, die fest ist. Vielleicht handelt es sich um einen anderen Anbieter, und Sie können keine Änderungen an dieser Hierarchie vornehmen. Ihre Absicht ist jedoch, dass Sie dieser Hierarchie neue polymorphe Methoden hinzufügen möchten. Das bedeutet, dass Sie normalerweise etwas zur Basisklassenschnittstelle hinzufügen müssen. Das Dilemma ist also, dass Sie der Basisklasse Methoden hinzufügen müssen, aber Sie können die Basisklasse nicht berühren. Wie kommst du damit um?

Das Designmuster, das diese Art von Problem löst, wird als "Besucher" (das letzte im Design Patterns-Buch) bezeichnet und baut auf dem im letzten Abschnitt gezeigten Schema der doppelten Verteilung auf.

Mit dem Besuchermuster können Sie die Schnittstelle des primären Typs erweitern, indem Sie eine separate Klassenhierarchie vom Typ Visitor erstellen, um die für den primären Typ ausgeführten Vorgänge zu virtualisieren. Die Objekte des primären Typs "akzeptieren" einfach den Besucher und rufen dann die dynamisch gebundene Elementfunktion des Besuchers auf.

1
wojcikstefan

Ich habe dieses Muster nicht verstanden, bis ich auf onkel bob article gestoßen bin und Kommentare gelesen habe .. _. Betrachten Sie den folgenden Code:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

Obwohl es gut aussieht, da es das Single Responsibility bestätigt, verstößt es gegen das Open/Closed Prinzip. Jedes Mal, wenn Sie einen neuen Mitarbeitertyp haben, müssen Sie ihn bei der Typüberprüfung hinzufügen. Und wenn nicht, werden Sie das zur Kompilierzeit nie erfahren.

Mit dem Besuchermuster können Sie Ihren Code sauberer machen, da er nicht gegen das Prinzip "offen/geschlossen" und nicht gegen die Einzelverantwortung verstößt. Und wenn Sie vergessen, Besuch zu implementieren, wird es nicht kompiliert:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

Die Magie ist, dass v.Visit(this) zwar gleich aussieht, sich jedoch tatsächlich unterscheidet, da es unterschiedliche Besucherüberlastungen aufruft.

1
Access Denied

Während ich das Wie und Wann verstanden habe, habe ich das Warum nie verstanden. Falls es jemandem mit einem Hintergrund in einer Sprache wie C++ hilft, möchten Sie lesen Sie dies sehr sorgfältig.

Für die Faulenzer verwenden wir das Besuchermuster, da "während virtuelle Funktionen in C++ dynamisch abgesetzt werden, die Funktionsüberladung statisch erfolgt".

Oder anders ausgedrückt, um sicherzustellen, dass CollideWith (ApolloSpacecraft &) aufgerufen wird, wenn Sie eine SpaceShip-Referenz übergeben, die tatsächlich an ein ApolloSpacecraft-Objekt gebunden ist.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}
1
Carl

Wenn Sie Funktionsobjekte für Vereinigungsdatentypen haben möchten, benötigen Sie ein Besuchermuster.

Sie fragen sich vielleicht, was Funktionsobjekte und Vereinigungsdatentypen sind. Dann lohnt es sich zu lesen http://www.ccs.neu.edu/home/matthias/htdc.html

0
Wei Qiu

Danke für die großartige Erklärung von @Federico A. Ramponi , ich habe dies gerade in Java gemacht. Hoffe, es könnte hilfreich sein. 

Ebenso wie @Konrad Rudolph ausgeführt wurde, handelt es sich tatsächlich um einen double-Dispatch, der zwei konkrete Instanzen gemeinsam verwendet, um die Laufzeitmethoden zu bestimmen. 

Daher ist es nicht erforderlich, ein common - Interface für den operation-Executor zu erstellen, solange das operation -Interface ordnungsgemäß definiert ist. 

import static Java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

Wie zu erwarten, wird eine common - Schnittstelle mehr Klarheit bringen, obwohl es in diesem Muster nicht der essential - Teil ist. 

import static Java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}
0
Hearen

ihre Frage ist, wann Sie wissen sollten:

ich codiere nicht zuerst mit dem Besuchermuster. Ich codiere Standard und warte auf die Notwendigkeit, aufzutreten und dann umzuwandeln. Nehmen wir also an, Sie haben mehrere Zahlungssysteme, die Sie einzeln installiert haben. Beim Auschecken könnten Sie viele if-Bedingungen (oder instanceOf) haben, zum Beispiel:

//psuedo code
    if(Paypal) 
    do Paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

nun stellen Sie sich vor, ich hätte 10 Zahlungsmethoden, es wird irgendwie hässlich. Wenn Sie also diese Art von Muster sehen, tritt der Besucher in die Hand, um all das herauszuschneiden, und Sie rufen am Ende so etwas wie folgt an:

new PaymentCheckoutVistor(paymentType).visit()

Wie Sie es implementieren können, sehen Sie an der Anzahl der Beispiele. Ich zeige Ihnen nur einen Anwendungsfall. 

0
j2emanue