Skip to content

Commit 06f52a3

Browse files
authored
Merge pull request #20 from urboob21/dev_branch
id 1763797213
2 parents f2a835c + a45107d commit 06f52a3

3 files changed

Lines changed: 289 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ set(APP_SOURCES
103103
"src/patterns/behavioral/Iterator.cpp"
104104
"src/patterns/behavioral/Mediator.cpp"
105105
"src/patterns/behavioral/Memento.cpp"
106+
"src/patterns/behavioral/Visitor.cpp"
106107
)
107108

108109
# Test files

docs/uml/patterns_behavioral_visitor.drawio.svg

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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 << "![](" << i->getPath() << ")\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

Comments
 (0)