Sök…


Exempel på besökarmönster i C ++

Istället för

struct IShape
{
    virtual ~IShape() = default;

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

Besökare kan användas:

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

Nu är de konkreta formerna:

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

sedan besökarna:

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

Och använd det:

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

Förväntad produktion:

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

Förklaring :

  • In void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); } , den statiska typen av this är känd, och så är den valda (vid sammanställningstiden) överbelastning void IVisitor::visit(const Square&); .

  • För square.accept(visitor); samtal, används den dynamiska sändningen via virtual för att veta vilka accept att ringa.

Fördelar :

  • Du kan lägga till ny funktionalitet ( SerializeAsXml , ...) till klassen IShape bara genom att lägga till en ny besökare.

Nackdelar :

  • Att lägga till en ny konkret form ( Triangle , ...) kräver att alla besökare ändras.

Alternativet att sätta alla funktioner som virtual metoder i IShape har motsatta fördelar och nackdelar: Att lägga till ny funktionalitet kräver att modifiera alla befintliga former, men att lägga till en ny form påverkar inte befintliga klasser.

Besökarmönstereksempel i java

Visitor kan du lägga till nya operationer eller metoder i en uppsättning klasser utan att ändra strukturen för dessa klasser.

Detta mönster är särskilt användbart när du vill centralisera en viss operation på ett objekt utan att utöka objektet eller utan att ändra objektet.

UML-diagram från wikipedia:

ange bildbeskrivning här

Kodavsnitt:

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

Förklaring:

  1. Visitable ( Element ) är ett gränssnitt och denna gränssnittsmetod måste läggas till i en uppsättning klasser.
  2. Visitor är ett gränssnitt som innehåller metoder för att utföra en VisitableVisitable element.
  3. GameVisitor är en klass som implementerar Visitor ( ConcreteVisitor ).
  4. Varje Visitable element accepterar Visitor och åberopar en relevant metod för Visitor .
  5. Du kan behandla Game som Element och konkreta spel som Chess,Checkers and Ludo som ConcreteElements .

I exemplet ovan är Chess, Checkers and Ludo tre olika spel (och Visitable klasser). På en fin dag har jag stött på ett scenario för att logga statistik över varje spel. Så utan att ändra enskild klass för att implementera statistikfunktionalitet kan du centralisera det ansvaret i GameVisitor klassen, vilket gör tricket för dig utan att ändra strukturen i varje spel.

produktion:

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

Använd fall / tillämpbarhet:

  1. Liknande operationer måste utföras på objekt av olika typer grupperade i en struktur
  2. Du måste utföra många distinkta och oberoende operationer. Det skiljer funktionen från objektets struktur
  3. Nya operationer måste läggas till utan förändring i objektstruktur
  4. Samla relaterade operationer i en enda klass snarare än att tvinga dig att ändra eller härleda klasser
  5. Lägg till funktioner i klassbibliotek där du antingen inte har källan eller inte kan ändra källan

Ytterligare referenser:

oodesign

sourcemaking

Besöksexempel i 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.
};

Exempel på användning:

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

Korsa stora föremål

Besökarmönstret kan användas för att korsa strukturer.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow