Design patterns
ビジターパターン
サーチ…
C ++でのビジターパターンの例
の代わりに
struct IShape
{
virtual ~IShape() = default;
virtual void print() const = 0;
virtual double area() const = 0;
virtual double perimeter() const = 0;
// .. and so on
};
訪問者を使用することができます:
// 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;
};
今具体的な形状:
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;
};
訪問者:
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.;
};
それを使う:
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;
}
期待される出力:
Square has an area of 4, and a perimeter of 8
Circle has an area of 3.14159, and a perimeter of 6.28319
説明 :
で
void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
、の静的タイプthis
知られており、したがって選択された(コンパイル時)過負荷であるvoid IVisitor::visit(const Square&);
。square.accept(visitor);
virtual
を介した動的ディスパッチは、どのaccept
を呼び出すかを知るために使用されます。
長所 :
- 新しい訪問者を追加するだけで、新しい機能(
SerializeAsXml
、...)をクラスIShape
追加することができます。
短所 :
- 新しいコンクリートシェイプ(
Triangle
、...)を追加するには、すべての訪問者を修正する必要があります。
ように、すべての機能を置く代わりのvirtual
のメソッドIShape
逆の長所と短所を持っている新しい機能を追加すると、すべての既存の形状を変更する必要はなく、新たな形状を追加する既存のクラスに影響を与えません。
javaの訪問者パターンの例
Visitor
パターンを使用すると、クラスの構造を変更することなく、クラスのセットに新しい操作やメソッドを追加できます。
このパターンは、オブジェクトを拡張せずにオブジェクトに特定の操作を集中させたい場合や、オブジェクトを変更しない場合に特に便利です。
ウィキペディアのUML図:
コードスニペット:
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);
}
}
}
説明:
-
Visitable
(Element
)はインターフェイスであり、このインターフェイスメソッドはクラスのセットに追加する必要があります。 -
Visitor
はインタフェースであり、Visitable
要素で操作を実行するメソッドが含まれています。 -
GameVisitor
はVisitor
インターフェイス(ConcreteVisitor
)を実装するクラスです。 - 各
Visitor
Visitable
要素はVisitor
を受け入れ、関連するVisitor
インタフェースのメソッドを呼び出す。 -
Game
をElement
として扱いChess,Checkers and Ludo
などの具体的なゲームをConcreteElements
として扱うことができます。
上記の例では、 Chess, Checkers and Ludo
は3種類のゲーム(およびVisitable
クラス)です。ある素晴らしい日に、私は各ゲームの統計を記録するシナリオに遭遇しました。したがって、統計機能を実装するために個々のクラスを変更することなく、その責任をGameVisitor
クラスに集中させることができます。これは、各ゲームの構造を変更することなくあなたのトリックを行います。
出力:
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
用途/適用範囲:
- 同様の操作は 、構造内でグループ化された異なるタイプのオブジェクトに対して実行する必要があります
- 多くのはっきりとした無関係な操作を実行する必要があります。 オペレーションをオブジェクト構造から分離します。
- オブジェクト構造の変更なしに新しい操作を追加する必要があります
- クラスを変更または派生させるのではなく、関連する操作を単一のクラスに集める
- ソースを持っていないか、ソースを変更できないクラスライブラリに関数を追加する
その他の参考資料:
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.
};
使用例:
// 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);
}
大きなオブジェクトをトラバースする
ビジターパターンを使用して構造をトラバースすることができます。
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);
}
}
}