Suche…


Beispiel für Besuchermuster in C ++

Anstatt

struct IShape
{
    virtual ~IShape() = default;

    virtual void print() const = 0;
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    // .. and so on
};

Besucher können verwendet werden:

// The concrete shapes
struct Square;
struct Circle;

// The visitor interface
struct IShapeVisitor
{
    virtual ~IShapeVisitor() = default;
    virtual void visit(const Square&) = 0;
    virtual void visit(const Circle&) = 0;
};

// The shape interface
struct IShape
{
    virtual ~IShape() = default;

    virtual void accept(IShapeVisitor&) const = 0;
};

Nun die konkreten Formen:

struct Point {
    double x;
    double y;
};

struct Circle : IShape
{
    Circle(const Point& center, double radius) : center(center), radius(radius) {}
    
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }

    Point center;
    double radius;
};

struct Square : IShape
{
    Square(const Point& topLeft, double sideLength) :
         topLeft(topLeft), sideLength(sideLength)
    {}

    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }

    Point topLeft;
    double sideLength;
};

dann die Besucher:

struct ShapePrinter : IShapeVisitor
{
    void visit(const Square&) override { std::cout << "Square"; }
    void visit(const Circle&) override { std::cout << "Circle"; }
};

struct ShapeAreaComputer : IShapeVisitor
{
    void visit(const Square& square) override
    {
        area = square.sideLength * square.sideLength;
    }

    void visit(const Circle& circle) override
    {
         area = M_PI * circle.radius * circle.radius;
    }

    double area = 0;
};

struct ShapePerimeterComputer : IShapeVisitor
{
    void visit(const Square& square) override { perimeter = 4. * square.sideLength; }
    void visit(const Circle& circle) override { perimeter = 2. * M_PI * circle.radius; }

    double perimeter = 0.;
};

Und benutze es:

const Square square = {{-1., -1.}, 2.};
const Circle circle{{0., 0.}, 1.};
const IShape* shapes[2] = {&square, &circle};

ShapePrinter shapePrinter;
ShapeAreaComputer shapeAreaComputer;
ShapePerimeterComputer shapePerimeterComputer;

for (const auto* shape : shapes) {
    shape->accept(shapePrinter);
    std::cout << " has an area of ";

    // result will be stored in shapeAreaComputer.area
    shape->accept(shapeAreaComputer);

    // result will be stored in shapePerimeterComputer.perimeter
    shape->accept(shapePerimeterComputer); 

    std::cout << shapeAreaComputer.area
              << ", and a perimeter of "
              << shapePerimeterComputer.perimeter
              << std::endl;
}

Erwartete Ausgabe:

Square has an area of 4, and a perimeter of 8
Circle has an area of 3.14159, and a perimeter of 6.28319

Demo

Erklärung :

  • In void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); } , der statische Typ this ist bekannt, und daher ist die (zur Kompilierzeit) ausgewählte Überladung void IVisitor::visit(const Square&); .

  • Für square.accept(visitor); Aufruf, der dynamische Versand durch virtual wird verwendet, um zu wissen, welche accept anzurufen ist.

Pros :

  • Sie können der Klasse IShape neue Funktionen ( SerializeAsXml , ...) hinzufügen, indem IShape einfach einen neuen Besucher hinzufügen.

Nachteile :

  • Das Hinzufügen einer neuen konkreten Form ( Triangle , ...) erfordert die Änderung aller Besucher.

Die Alternative, alle Funktionen als virtual Methoden in IShape hat andere Vor- und Nachteile: Das Hinzufügen neuer Funktionen erfordert das Ändern aller vorhandenen Formen. Das Hinzufügen einer neuen Form wirkt sich jedoch nicht auf vorhandene Klassen aus.

Muster für Besucher in Java

Visitor können Sie einer Gruppe von Klassen neue Operationen oder Methoden hinzufügen, ohne die Struktur dieser Klassen zu ändern.

Dieses Muster ist besonders nützlich, wenn Sie einen bestimmten Vorgang für ein Objekt zentralisieren möchten, ohne das Objekt zu erweitern. Oder ohne das Objekt zu ändern.

UML-Diagramm aus Wikipedia:

Geben Sie hier die Bildbeschreibung ein

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 einer Gruppe von Klassen hinzugefügt werden.
  2. Visitor ist eine Schnittstelle, die Methoden enthält, um eine Operation mit Visitable Elementen Visitable .
  3. GameVisitor ist eine Klasse, die ein Visitor Interface ( ConcreteVisitor ) implementiert.
  4. Jedes Visitable Element akzeptiert Visitor und Visitable eine relevante Methode der Visitor Schnittstelle auf.
  5. Sie können Game als Element und konkrete Spiele wie Chess,Checkers and Ludo als ConcreteElements .

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

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

Anwendungsfälle / Anwendbarkeit:

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

Zusätzliche referenzen:

Ooodesign

Beschaffung von Lebensmitteln

Besucherbeispiel in C ++

// A simple class hierarchy that uses the visitor to add functionality.
//
class VehicleVisitor;
class Vehicle
{
    public:
        // To implement the visitor pattern
        // The class simply needs to implement the accept method 
        // That takes a reference to a visitor object that provides
        // new functionality.
        virtual void accept(VehicleVisitor& visitor) = 0
};
class Plane: public Vehicle
{
    public:
        // Each concrete representation simply calls the visit()
        // method on the visitor object passing itself as the parameter.
        virtual void accept(VehicleVisitor& visitor) {visitor.visit(*this);}

        void fly(std::string const& destination);
};
class Train: public Vehicle
{
    public:
        virtual void accept(VehicleVisitor& visitor) {visitor.visit(*this);}

        void locomote(std::string const& destination);
};
class Automobile: public Vehicle
{
    public:
        virtual void accept(VehicleVisitor& visitor) {visitor.visit(*this);}

        void drive(std::string const& destination);
};

class VehicleVisitor
{
    public:
        // The visitor interface implements one method for each class in the
        // hierarchy. When implementing new functionality you just create the
        // functionality required for each type in the appropriate method.

        virtual void visit(Plane& object)      = 0;
        virtual void visit(Train& object)      = 0;
        virtual void visit(Automobile& object) = 0;

    // Note: because each class in the hierarchy needs a virtual method
    // in visitor base class this makes extending the hierarchy ones defined
    // hard.
};

Ein Beispiel zur Verwendung:

// Add the functionality `Move` to an object via a visitor.
class MoveVehicleVisitor
{
    std::string const& destination;
    public:
        MoveVehicleVisitor(std::string const& destination)
            : destination(destination)
    {}
    virtual void visit(Plane& object)      {object.fly(destination);}
    virtual void visit(Train& object)      {object.locomote(destination);}
    virtual void visit(Automobile& object) {object.drive(destination);}
};

int main()
{
    MoveVehicleVisitor  moveToDenver("Denver");
    Vehicle&            object = getObjectToMove();
    object.accept(moveToDenver);
}

Große Objekte durchqueren

Mit dem Besuchermuster können Strukturen durchlaufen werden.

class GraphVisitor;
class Graph
{
    public:
        class Node
        {
            using Link = std::set<Node>::iterator;
            std::set<Link>   linkTo;
            public:
                void accept(GraphVisitor& visitor);
        };

        void accept(GraphVisitor& visitor);

    private:
        std::set<Node>  nodes;
};

class GraphVisitor
{
    std::set<Graph::Node*>  visited;
    public:
        void visit(Graph& graph)
        {
            visited.clear();
            doVisit(graph);
        } 
        bool visit(Graph::Node& node)
        {
            if (visited.find(&node) != visited.end()) {
                return false;
            }
            visited.insert(&node);
            doVisit(node);
            return true;
        }
    private: 
        virtual void doVisit(Graph& graph)      = 0;
        virtual void doVisit(Graph::Node& node) = 0;
};

void accept(GraphVisitor& visitor)
{
    // Pass the graph to the visitor.
    visitor.visit(*this);

    // Then do a depth first search of the graph.
    // In this situation it is the visitors responsibility
    // to keep track of visited nodes.
    for(auto& node: nodes) {
        node.accept(visitor);
    }
}
void Graph::Node::accept(GraphVisitor& visitor)
{
    // Tell the visitor it is working on a node and see if it was 
    // previously visited.
    if (visitor.visit(*this)) {

        // The pass the visitor to all the linked nodes.
        for(auto& link: linkTo) {
            link->accept(visitor);
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow