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.

3voto

Tomás Escamez Punkte 522

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 wäre für den gesamten Entwurf schmerzhaft.

Gleichzeitig wirft das Hinzufügen der "Walk"-Methode neue Fragen auf. Was ist mit "Fressen" oder "Schlafen"? Müssen wir wirklich für jede neue Aktion oder Operation, die wir hinzufügen wollen, eine neue Methode zur Tierhierarchie hinzufügen? Das ist unschön und vor allem können wir die Animal-Schnittstelle nie schließen. Mit dem Besucher-Muster können wir also neue Methoden zur Hierarchie hinzufügen, ohne die Hierarchie zu verändern!

Prüfen Sie also einfach dieses C#-Beispiel und führen Sie es aus:

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

3voto

wojcikstefan Punkte 837

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

Die Annahme ist, dass Sie eine primäre Klassenhierarchie haben, die feststeht; vielleicht ist sie von einem anderen Anbieter und Sie können keine Änderungen an dieser Hierarchie vornehmen. Sie möchten jedoch neue polymorphe Methoden zu dieser Hierarchie hinzufügen, was bedeutet, dass Sie normalerweise etwas zur Schnittstelle der Basisklasse hinzufügen müssen. Das Dilemma besteht also darin, dass Sie der Basisklasse Methoden hinzufügen müssen, aber die Basisklasse nicht berühren können. Wie kann man das umgehen?

Das Entwurfsmuster, das diese Art von Problem löst, wird "Visitor" genannt (das letzte Muster im Buch Design Patterns) und baut auf dem im letzten Abschnitt gezeigten Schema der doppelten Verteilung auf.

Das Visitor-Pattern ermöglicht es, die Schnittstelle des primären Typs zu erweitern, indem eine separate Klassenhierarchie des Typs Visitor erstellt wird, um die auf dem primären Typ ausgeführten Operationen zu virtualisieren. Die Objekte des primären Typs "akzeptieren" einfach den Besucher und rufen dann die dynamisch gebundene Mitgliedsfunktion des Besuchers auf.

1voto

Carl Punkte 41134

Ich habe zwar das Wie und Wann verstanden, aber nie das Warum. Für den Fall, dass es jemandem mit einem Hintergrund in einer Sprache wie C++ hilft, möchten Sie dies lesen sehr sorgfältig.

Für faule Menschen verwenden wir das Besuchermuster, weil "Während virtuelle Funktionen in C++ dynamisch abgewickelt werden, erfolgt die Funktionsüberladung statisch". .

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

0voto

Hearen Punkte 6799

Danke für die großartige Erklärung von @Federico A. Ramponi Ich habe das gerade in java Version. Ich hoffe, sie ist hilfreich.

Genauso wie @Konrad Rudolph darauf hingewiesen, ist es eigentlich ein Doppelversand mit zwei konkreten Instanzen zusammen, um die Laufzeitmethoden zu bestimmen.

Es besteht also eigentlich keine Notwendigkeit, eine gemeinsame Schnittstelle für die Betrieb Vollstrecker, solange wir die Betrieb Schnittstelle richtig definiert.

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, ist ein gemeinsame Schnittstelle wird uns mehr Klarheit bringen, obwohl es eigentlich nicht die Wesentlich Teil in diesem Muster.

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

0voto

j2emanue Punkte 56131

Ihre Frage ist, wann Sie es wissen:

ich nicht zuerst Code mit Besucher-Muster. ich Code Standard und warten auf die Notwendigkeit auftreten & dann refactor. so lässt sagen, Sie haben mehrere Zahlungssysteme, die Sie eine zu einer Zeit installiert. An der Kasse Zeit könnten Sie viele Wenn-Bedingungen (oder instanceOf) haben, zum Beispiel:

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

Stellen Sie sich nun vor, ich hätte 10 Zahlungsarten, dann wird es ziemlich unschön. Also, wenn Sie sehen, dass diese Art von Muster auftritt Besucher kommt in handly zu trennen, dass alle aus und Sie am Ende ruft etwas wie dieses danach:

new PaymentCheckoutVistor(paymentType).visit()

Sie können sehen, wie es aus der Anzahl der Beispiele hier zu implementieren, im nur zeigen Ihnen einen Anwendungsfall.

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