Design patterns
Bezoekerspatroon
Zoeken…
Voorbeeld van bezoekerspatroon in C ++
In plaats van
struct IShape
{
virtual ~IShape() = default;
virtual void print() const = 0;
virtual double area() const = 0;
virtual double perimeter() const = 0;
// .. and so on
};
Bezoekers kunnen worden gebruikt:
// 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 de concrete vormen:
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;
};
dan de bezoekers:
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.;
};
En gebruik het:
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;
}
Verwachte resultaten:
Square has an area of 4, and a perimeter of 8
Circle has an area of 3.14159, and a perimeter of 6.28319
Uitleg :
In
void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
, het statische typethis
is bekend, en dus is de gekozen (tijdens het compileren) overbelastingvoid IVisitor::visit(const Square&);
.Voor
square.accept(visitor);
call, wordt de dynamische verzending viavirtual
gebruikt om te weten welke teaccept
oproepen.
Voordelen :
- U kunt nieuwe functionaliteit (
SerializeAsXml
, ...) toevoegen aan de klasseIShape
door een nieuwe bezoeker toe te voegen.
Nadelen :
- Het toevoegen van een nieuwe concrete vorm (
Triangle
, ...) vereist het aanpassen van alle bezoekers.
Het alternatief van het plaatsen van alle functionaliteiten als virtual
methoden in IShape
heeft tegenovergestelde voor- en nadelen: het toevoegen van nieuwe functionaliteit vereist het aanpassen van alle bestaande vormen, maar het toevoegen van een nieuwe vorm heeft geen invloed op bestaande klassen.
Voorbeeld van bezoekerspatroon in Java
Visitor
kunt u nieuwe bewerkingen of methoden toevoegen aan een set klassen zonder de structuur van die klassen te wijzigen.
Dit patroon is vooral handig als u een bepaalde bewerking op een object wilt centraliseren zonder het object uit te breiden of zonder het object te wijzigen.
UML-diagram van wikipedia:
Codefragment:
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);
}
}
}
Uitleg:
-
Visitable
(Element
) is een interface en deze interfacemethode moet worden toegevoegd aan een set klassen. -
Visitor
is een interface die methoden bevat om een bewerking uit te voeren opVisitable
elementen. -
GameVisitor
is een klasse die deVisitor
(ConcreteVisitor
) implementeert. - Elk
Visitable
element accepteertVisitor
en roept een relevante methode van deVisitor
. - Je kunt
Game
alsElement
en concrete spellen zoalsChess,Checkers and Ludo
alsConcreteElements
.
In het bovenstaande voorbeeld zijn Chess, Checkers and Ludo
drie verschillende spellen (en Visitable
klassen). Op een mooie dag ben ik een scenario tegengekomen om statistieken van elk spel te loggen. Dus zonder de individuele klasse te wijzigen om statistische functionaliteit te implementeren, kunt u die verantwoordelijkheid centraliseren in de GameVisitor
klasse, die voor u werkt zonder de structuur van elke game te wijzigen.
output:
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
Gebruiksgevallen / Toepasselijkheid:
- Soortgelijke bewerkingen moeten worden uitgevoerd op objecten van verschillende typen gegroepeerd in een structuur
- U moet veel verschillende en niet-gerelateerde bewerkingen uitvoeren. Het scheidt Operatie van objecten Structuur
- Nieuwe bewerkingen moeten worden toegevoegd zonder wijziging van de objectstructuur
- Verzamel gerelateerde bewerkingen in één klasse in plaats van u te dwingen klassen te wijzigen of af te leiden
- Voeg functies toe aan klassenbibliotheken waarvoor u de bron niet hebt of de bron niet kunt wijzigen
Aanvullende referenties:
Bezoeker voorbeeld 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.
};
Een voorbeeld gebruik:
// 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);
}
Grote objecten doorkruisen
Het bezoekerspatroon kan worden gebruikt om structuren te doorkruisen.
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);
}
}
}