1+ // Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.
2+ // Appicability:
3+ // (*) when you need to perform an operation on all elements of a complex object structure (for example, an object tree, document).
4+ // (**) to clean up the business logic of auxiliary behaviors.
5+ // (**) when a behavior makes sense only in some classes of a class hierarchy, but not in others.
6+ // UML: docs/uml/patterns_behavioral_visitor.drawio.svg
7+
8+ #include < iostream>
9+ #include < string>
10+ #include < vector>
11+
12+ namespace
13+ {
14+ namespace Visitor
15+ {
16+ class TextConcreteElement ;
17+ class ImageConcreteElement ;
18+ class TableConcreteElement ;
19+
20+ /* *
21+ * The Visitor interface declares a set of visiting methods that can take concrete elements of an object structure as arguments.
22+ * These methods may have the same names if the program is written in a language that supports overloading,
23+ * but the type of their parameters must be different.
24+ */
25+ class IVisitor
26+ {
27+ public:
28+ virtual ~IVisitor () = default ;
29+
30+ virtual void visitText (const TextConcreteElement *t) = 0;
31+ virtual void visitImage (const ImageConcreteElement *i) = 0;
32+ virtual void visitTable (const TableConcreteElement *t) = 0;
33+ };
34+
35+ /* *
36+ * The Element interface declares a method for “accepting” visitors.
37+ * This method should have one parameter declared with the type of the visitor interface.
38+ */
39+ class IElement
40+ {
41+ public:
42+ virtual ~IElement () = default ;
43+ // [P1]
44+ // if we implement exportHtml(), exportMarkdown(), exportJson(), etc.. here,
45+ // every time we add a new export format we must modify ALL element classes.
46+ // This violates the Open/Closed Principle
47+ // and makes each element hold multiple unrelated behaviors.
48+ // => Use `Visitor Pattern` to separate export logic from the element classes.
49+
50+ virtual void accept (IVisitor *visitor) = 0;
51+ };
52+
53+ /* *
54+ * The complex object structure
55+ */
56+ class DocumentConcreteStructure
57+ {
58+ private:
59+ std::vector<IElement *> m_elements;
60+
61+ public:
62+ ~DocumentConcreteStructure ()
63+ {
64+ for (IElement *e : this ->m_elements )
65+ {
66+ delete e;
67+ }
68+ m_elements.clear ();
69+ }
70+
71+ void add (IElement *e)
72+ {
73+ m_elements.push_back (e);
74+ }
75+
76+ std::vector<IElement *> &get ()
77+ {
78+ return this ->m_elements ;
79+ };
80+ };
81+
82+ /* *
83+ * Each Concrete Element must implement the acceptance method.
84+ * The purpose of this method is to redirect the call to the proper visitor’s method corresponding to the current element class.
85+ * Be aware that even if a base element class implements this method, all subclasses must still override this method
86+ * in their own classes and call the appropriate method on the visitor object.
87+ */
88+ class TextConcreteElement : public IElement
89+ {
90+ private:
91+ std::string m_content;
92+
93+ public:
94+ explicit TextConcreteElement (const std::string &c) : m_content{c} {};
95+ std::string getContent () const
96+ {
97+ return this ->m_content ;
98+ }
99+
100+ void accept (IVisitor *visitor) override
101+ {
102+ visitor->visitText (this );
103+ }
104+ };
105+
106+ class ImageConcreteElement : public IElement
107+ {
108+ private:
109+ std::string m_path;
110+
111+ public:
112+ explicit ImageConcreteElement (const std::string &p) : m_path{p} {};
113+
114+ std::string getPath () const
115+ {
116+ return this ->m_path ;
117+ }
118+
119+ void accept (IVisitor *visitor) override
120+ {
121+ visitor->visitImage (this );
122+ }
123+ };
124+
125+ class TableConcreteElement : public IElement
126+ {
127+ private:
128+ int m_rows, m_cols;
129+
130+ public:
131+ TableConcreteElement (int r, int c) : m_rows{r}, m_cols{c} {};
132+ int getRows () const { return this ->m_rows ; }
133+ int getCols () const { return this ->m_cols ; }
134+
135+ void accept (IVisitor *visitor) override
136+ {
137+ visitor->visitTable (this );
138+ }
139+ };
140+
141+ /* *
142+ * Each Concrete Visitor implements several versions of the same behaviors,
143+ * tailored for different concrete element classes.
144+ */
145+ class HtmlExportConcreteVisitor : public IVisitor
146+ {
147+ void visitText (const TextConcreteElement *t) override
148+ {
149+ std::cout << " <p>" + t->getContent () + " </p>\n " ;
150+ }
151+
152+ void visitImage (const ImageConcreteElement *i) override
153+ {
154+ std::cout << " <img src=\" " + i->getPath () + " \" />\n " ;
155+ }
156+
157+ void visitTable (const TableConcreteElement *t) override
158+ {
159+ std::cout << " <table>\n " ;
160+ for (int r = 0 ; r < t->getRows (); ++r)
161+ {
162+ std::cout << " <tr>" ;
163+ for (int c = 0 ; c < t->getCols (); ++c)
164+ {
165+ std::cout << " <td>cell</td>" ;
166+ }
167+ std::cout << " </tr>\n " ;
168+ }
169+ std::cout << " </table>\n " ;
170+ }
171+ };
172+
173+ class JsonExportConcreteVisitor : public IVisitor
174+ {
175+ void visitText (const TextConcreteElement *t) override
176+ {
177+ std::cout << " { \" type\" : \" text\" , \" content\" : \" " << t->getContent () << " \" }\n " ;
178+ }
179+ void visitImage (const ImageConcreteElement *i) override
180+ {
181+ std::cout << " { \" type\" : \" image\" , \" path\" : \" " << i->getPath () << " \" }\n " ;
182+ }
183+
184+ void visitTable (const TableConcreteElement *t) override
185+ {
186+ std::cout << " { \" type\" : \" table\" , \" rows\" : " << t->getRows ()
187+ << " , \" cols\" : " << t->getCols () << " }\n " ;
188+ }
189+ };
190+
191+ class MarkdownExportConcreteVisitor : public IVisitor
192+ {
193+ void visitText (const TextConcreteElement *t) override
194+ {
195+ std::cout << t->getContent () << " \n\n " ;
196+ }
197+
198+ void visitImage (const ImageConcreteElement *i) override
199+ {
200+ std::cout << "  << " )\n\n " ;
201+ }
202+
203+ void visitTable (const TableConcreteElement *t) override
204+ {
205+ for (int r = 0 ; r < t->getRows (); ++r)
206+ {
207+ for (int c = 0 ; c < t->getCols (); ++c)
208+ {
209+ std::cout << " | cell " ;
210+ }
211+ std::cout << " |\n " ;
212+ }
213+ std::cout << " \n " ;
214+ }
215+ };
216+
217+ /* *
218+ * clients aren’t aware of all the concrete element classes because they work with objects from that collection via some abstract interface.
219+ */
220+ namespace Client
221+ {
222+ void clientCode (DocumentConcreteStructure *const doc, IVisitor *const visitor)
223+ {
224+ // [P2] Instead of adding exportHtml(), exportMarkdown(), exportJson() in each element,
225+ // we use the Visitor pattern to separate data from behavior.
226+ // This avoids bloating element classes with multiple unrelated functions
227+ // and makes it easier to add new export formats without modifying the elements.
228+ for (IElement *ele : doc->get ())
229+ {
230+ ele->accept (visitor);
231+ }
232+ }
233+ }
234+ void run ()
235+ {
236+ DocumentConcreteStructure *document = new DocumentConcreteStructure ();
237+ // Add images
238+ document->add (new ImageConcreteElement (" header.png" ));
239+ document->add (new ImageConcreteElement (" diagram1.png" ));
240+ document->add (new ImageConcreteElement (" chart.png" ));
241+
242+ // Add text paragraphs
243+ document->add (new TextConcreteElement (" Introduction: This document demonstrates the Visitor pattern." ));
244+ document->add (new TextConcreteElement (" Section 1: Visitor allows adding new operations without modifying elements." ));
245+ document->add (new TextConcreteElement (" Section 2: Elements are data holders, visitors implement behaviors." ));
246+
247+ // Add tables
248+ document->add (new TableConcreteElement (3 , 4 )); // e.g., 3 rows x 4 columns table
249+ document->add (new TableConcreteElement (2 , 2 )); // smaller table
250+ document->add (new TableConcreteElement (4 , 3 )); // another table
251+
252+ // Add more text
253+ document->add (new TextConcreteElement (" Conclusion: Using Visitor pattern makes code open for extension but closed for modification." ));
254+
255+ IVisitor *visitor = new HtmlExportConcreteVisitor ();
256+ std::cout << " ===HTML Export ===\n " ;
257+ Client::clientCode (document, visitor);
258+ std::cout << " ==================\n " ;
259+
260+ visitor = new JsonExportConcreteVisitor ();
261+ std::cout << " ===JSON Export ===\n " ;
262+ Client::clientCode (document, visitor);
263+ std::cout << " ==================\n " ;
264+
265+ visitor = new MarkdownExportConcreteVisitor ();
266+ std::cout << " ===MD Export ===\n " ;
267+ Client::clientCode (document, visitor);
268+ std::cout << " ==================\n " ;
269+ delete visitor;
270+ delete document;
271+ }
272+ }
273+ }
274+
275+ struct VisitorAutoRunner
276+ {
277+ VisitorAutoRunner ()
278+ {
279+ std::cout << " \n --- Visitor Pattern Example ---\n " ;
280+ Visitor::run ();
281+ }
282+ };
283+
284+ static VisitorAutoRunner instance;
0 commit comments