Design patterns
Modello di visitatore
Ricerca…
Esempio di pattern Visitor in C ++
Invece di
struct IShape
{
virtual ~IShape() = default;
virtual void print() const = 0;
virtual double area() const = 0;
virtual double perimeter() const = 0;
// .. and so on
};
I visitatori possono essere utilizzati:
// 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;
};
Ora le forme concrete:
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;
};
quindi i visitatori:
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.;
};
E usalo:
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;
}
Uscita prevista:
Square has an area of 4, and a perimeter of 8
Circle has an area of 3.14159, and a perimeter of 6.28319
Spiegazione :
In
void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
, il tipo statico dithis
è noto, quindi il sovraccarico scelto (in fase di compilazione) èvoid IVisitor::visit(const Square&);
.Per
square.accept(visitor);
chiamata, l'invio dinamico tramitevirtual
viene utilizzato per sapere qualeaccept
chiamare.
Pro :
- Puoi aggiungere nuove funzionalità (
SerializeAsXml
, ...) alla classeIShape
semplicemente aggiungendo un nuovo visitatore.
Contro :
- L'aggiunta di una nuova forma concreta (
Triangle
, ...) richiede la modifica di tutti i visitatori.
L'alternativa di mettere tutte le funzionalità come metodi virtual
in IShape
ha opposti vantaggi e svantaggi: l'aggiunta di nuove funzionalità richiede la modifica di tutte le forme esistenti, ma l'aggiunta di una nuova forma non ha alcun impatto sulle classi esistenti.
Esempio di modello di visitatore in java
Visitor
pattern Visitor
consente di aggiungere nuove operazioni o metodi a un set di classi senza modificare la struttura di tali classi.
Questo modello è particolarmente utile quando si desidera centralizzare una particolare operazione su un oggetto senza estendere l'oggetto o senza modificare l'oggetto.
Diagramma UML da wikipedia:
Snippet di codice:
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);
}
}
}
Spiegazione:
-
Visitable
(Element
) è un'interfaccia e questo metodo di interfaccia deve essere aggiunto a un insieme di classi. -
Visitor
è un'interfaccia, che contiene metodi per eseguire un'operazione su elementiVisitable
. -
GameVisitor
è una classe che implementa l'interfacciaVisitor
(ConcreteVisitor
). - Ogni elemento
Visitable
accettaVisitor
e invoca un metodo rilevante di interfacciaVisitor
. - Puoi trattare
Game
asElement
e giochi di cemento comeChess,Checkers and Ludo
comeConcreteElements
.
Nell'esempio sopra, Chess, Checkers and Ludo
sono tre giochi diversi (e classi Visitable
). In un bel giorno, ho incontrato uno scenario per registrare le statistiche di ogni gioco. Quindi, senza modificare la classe individuale per implementare la funzionalità statistica, è possibile centralizzare tale responsabilità nella classe GameVisitor
, che fa il trucco per te senza modificare la struttura di ogni gioco.
produzione:
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
Casi d'uso / Applicabilità:
- Operazioni simili devono essere eseguite su oggetti di diversi tipi raggruppati in una struttura
- È necessario eseguire molte operazioni distinte e non correlate. Separa l'operazione dagli oggetti Struttura
- Nuove operazioni devono essere aggiunte senza modifiche nella struttura dell'oggetto
- Raccogli le operazioni correlate in una singola classe anziché costringerti a modificare o ricavare classi
- Aggiungi funzioni alle librerie di classi per le quali non hai la sorgente o non puoi cambiare la fonte
Ulteriori riferimenti:
Esempio di visitatore 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.
};
Un esempio di utilizzo:
// 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);
}
Attraversando oggetti di grandi dimensioni
Il modello visitatore può essere utilizzato per attraversare strutture.
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);
}
}
}