Visitor Pattern example in C
suggest changeInstead of
struct IShape { virtual ~IShape() = default; virtual void print() const = 0; virtual double area() const = 0; virtual double perimeter() const = 0; // .. and so on };
Visitors can be used:
// 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; };
Now the concrete shapes:
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; };
then the visitors:
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.; };
And use it:
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; }
Expected output:
Square has an area of 4, and a perimeter of 8 Circle has an area of 3.14159, and a perimeter of 6.28319
Explanation:
- In
void Square::accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
, the static type ofthis
is known, and so the chosen (at compile time) overload isvoid IVisitor::visit(const Square&);
. - For
square.accept(visitor);
call, the dynamic dispatch throughvirtual
is used to know whichaccept
to call.
Pros:
- You may add new functionality (
SerializeAsXml
, …) to the classIShape
just by adding a new visitor.
Cons:
- Adding a new concrete shape (
Triangle
, …) requires to modifying all visitors.
The alternative of putting all functionalities as virtual
methods in IShape
has opposite pros and cons: Adding new functionality requires to modify all existing shapes, but adding a new shape doesn’t impact existing classes.
Found a mistake? Have a question or improvement idea?
Let me know.
Table Of Contents