Quoting wikipedia
In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. It is one way to follow the open/closed principle.
As mentioned, the goal is to separate the algorithm from objects which it operates on.
As a result, it allow us to add new operations without modifying the objects.
In C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef INCLUDE_VISITOR_H #define INCLUDE_VISITOR_H struct Rider ;struct Skier ;struct IVisitor { virtual ~IVisitor() = default ; virtual void visit (Rider *student) = 0 ; virtual void visit (Skier *student) = 0 ; }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef INCLUDE_STUDENT_H #define INCLUDE_STUDENT_H #include "ivisitor.h" struct Student { virtual ~Student() = default ; virtual void accept (IVisitor *visitor) = 0 ; }; struct Skier : public Student { void accept (IVisitor *visitor) override { visitor->visit(this ); } }; struct Rider : public Student { void accept (IVisitor *visitor) override { visitor->visit(this ); } }; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include "student.h" #include "student_visitor.h" #include <iostream> #include <memory> #include <vector> using namespace std ;struct RentalVisitor : IVisitor { void visit (Rider *rider) { std ::cout << "Prepare snowboard..." << std ::endl ; } void visit (Skier *skier) { std ::cout << "Prepare ski..." << std ::endl ; }; }; struct TeachVisitor : IVisitor { void visit (Rider *rider) { std ::cout << "Teach a rider" << std ::endl ; } void visit (Skier *skier) { std ::cout << "Teach a skier" << std ::endl ; }; }; int main (int , char **) { vector <unique_ptr <Student>> students; students.emplace_back(make_unique<Skier>()); students.emplace_back(make_unique<Rider>()); RentalVisitor rental_visitor; TeachVisitor teach_visitor; for (auto &s : students) { s->accept(&rental_visitor); s->accept(&teach_visitor); } }
In Python Traditional double dispatch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 ''' Implement visitor pattern via double dispatch. ''' from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Listclass IVisitor (ABC) : ''' Visitor interface ''' @abstractmethod def visit_skier (self, student) : ... @abstractmethod def visit_rider (self, student) : ... class Student (ABC) : @abstractmethod def accept (self, visitor) : ... class Skier (Student) : def accept (self, visitor) : visitor.visit_skier(self) class Rider (Student) : def accept (self, visitor) : visitor.visit_rider(self) class RentalVisitor (IVisitor) : def visit_skier (self, student) : print('Prepare ski...' ) def visit_rider (self, student) : print('Prepare snowboard...' ) class TeachVisitor (IVisitor) : def visit_skier (self, student) : print('Teach skier...' ) def visit_rider (self, student) : print('Teach rider...' ) def main () : rental_visitor = RentalVisitor() teach_visitor = TeachVisitor() students: List[Student] = [Skier(), Skier(), Rider()] for s in students: s.accept(rental_visitor) s.accept(teach_visitor) if __name__ == "__main__" : main()
Annotation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Listclass Student (ABC) : ... class Skier (Student) : ... class Rider (Student) : ... def _qualname (obj) : '''Get the fully-qualified name of an object (including module).''' return obj.__module__ + '.' + obj.__qualname__ def _declaring_class (obj) : '''Get the name of the class that declared an object.''' name = _qualname(obj) return name[:name.rfind('.' )] _methods = {} def _visitor_impl (self, arg) : '''Actual visitor method implementation.''' method = _methods[(_qualname(type(self)), type(arg))] return method(self, arg) def visitor (arg_type) : '''Decorator that creates a visitor method.''' def decorator (fn) : declaring_class = _declaring_class(fn) _methods[(declaring_class, arg_type)] = fn return _visitor_impl return decorator class RentalVisitor : @visitor(Skier) def visit (self, student) : print('Prepare ski...' ) @visitor(Rider) def visit (self, student) : print('Prepare snowboard...' ) def main () : rental_visitor = RentalVisitor() students: List[Student] = [Skier(), Skier(), Rider()] for s in students: rental_visitor.visit(s) print(_methods) if __name__ == '__main__' : main()