Szukaj…


Przykład wzorca gościa w C ++

Zamiast

struct IShape
{
    virtual ~IShape() = default;

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

Z odwiedzających można korzystać:

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

Teraz konkretne kształty:

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

następnie goście:

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

I użyj tego:

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

Oczekiwany wynik:

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

Próbny

Objaśnienie :

  • In void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); } , typ statyczny this jest znany, więc wybrane przeciążenie (w czasie kompilacji) jest void IVisitor::visit(const Square&); .

  • Dla square.accept(visitor); call, dynamiczna wysyłka za pośrednictwem virtual służy do określenia, kto accept połączenie.

Plusy :

  • Możesz dodać nową funkcjonalność ( SerializeAsXml , ...) do klasy IShape po prostu dodając nowego użytkownika.

Wady :

  • Dodanie nowego betonowego kształtu ( Triangle , ...) wymaga modyfikacji wszystkich odwiedzających.

Alternatywa umieszczenia wszystkich funkcji jako metod virtual w IShape ma przeciwne zalety i wady: Dodanie nowej funkcjonalności wymaga modyfikacji wszystkich istniejących kształtów, ale dodanie nowego kształtu nie wpływa na istniejące klasy.

Przykład wzoru gościa w java

Wzorzec Visitor pozwala dodawać nowe operacje lub metody do zestawu klas bez modyfikowania struktury tych klas.

Ten wzorzec jest szczególnie przydatny, gdy chcesz scentralizować określoną operację na obiekcie bez rozszerzania obiektu lub bez modyfikowania obiektu.

Schemat UML z wikipedii:

wprowadź opis zdjęcia tutaj

Fragment kodu:

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

Wyjaśnienie:

  1. Visitable ( Element ) to interfejs i tę metodę interfejsu należy dodać do zestawu klas.
  2. Visitor to interfejs, który zawiera metody wykonywania operacji na elementach Visitable .
  3. GameVisitor jest klasa, który realizuje Visitor interfejs ( ConcreteVisitor ).
  4. Każdy Visitable elementem akceptują Visitor i wywołać odpowiednią metodę Visitor interfejsu.
  5. Możesz traktować Game jako Element a konkretne gry, takie jak Chess,Checkers and Ludo jako ConcreteElements .

W powyższym przykładzie Chess, Checkers and Ludo to trzy różne gry (i klasy Visitable ). Pewnego pięknego dnia napotkałem scenariusz rejestrowania statystyk każdej gry. Bez modyfikowania poszczególnych klas w celu zaimplementowania funkcji statystycznych możesz scentralizować tę odpowiedzialność w klasie GameVisitor , co GameVisitor bez modyfikowania struktury każdej gry.

wynik:

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

Przypadki użycia / zastosowanie:

  1. Podobne operacje należy wykonać na obiektach różnych typów zgrupowanych w strukturze
  2. Musisz wykonać wiele różnych i niepowiązanych operacji. Oddziela Operację od struktury obiektów
  3. Nowe operacje muszą zostać dodane bez zmiany struktury obiektu
  4. Zbierz powiązane operacje w jedną klasę zamiast zmuszać cię do zmiany lub wyprowadzenia klas
  5. Dodaj funkcje do bibliotek klas, dla których albo nie masz źródła, albo nie możesz go zmienić

Dodatkowe referencje:

oodesign

zaopatrzenie

Przykład gościa w 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.
};

Przykładowe użycie:

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

Przemierzanie dużych obiektów

Wzorzec gościa może służyć do przechodzenia przez struktury.

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow