Design Pattern Examples
Overview of object-oriented design patterns
Visitor_Visitor_Class.cs
Go to the documentation of this file.
1
5
6// The Visitor pattern is a way of separating data from operations where the
7// operations can be applied after the list of data is created all while
8// leaving the data and operations as objects. The visitor pattern works best
9// where the number of data classes changes very infrequently. The data
10// classes and associated Visitor class must be engineered to support the
11// Visitor pattern.
12//
13// Probably the best way to think of the Visitor pattern is as a way to apply
14// operations to a collection of data classes without changing the data
15// classes themselves. The data classes are set up to support a visitor class
16// and from then on can support any visitor that derives from that visitor
17// class.
18//
19// All Visitor classes derive from a known Visitor base class provided by the
20// the data class implementation -- natural since the data class
21// implementation must support visitors so it must dictate the interface to
22// the visitors.
23//
24// As originally envisioned, the Visitor pattern is a rather clunky pattern.
25//
26// Here's the first clunky part: An operation is defined as a class derived
27// from the Visitor base class. On that base class are a number of virtual
28// methods called visit() that each take a single parameter the type of which
29// is one of the derived Element classes. If there are five derived Element
30// classes, there are five versions of the visit() method. These virtual
31// methods do nothing by default. Each derived visitor class that provides an
32// operation must override the appropriate visit() method for the Element type
33// that operation is interested in.
34//
35// Here's the second clunky part: The base Element class must define a pure
36// virtual method typically called accept() that accepts the Visitor base
37// class. Then every derived Element class must implement the accept()
38// method. (Technically, the base Element class could have a default
39// implementation of visit() but since the visitor pattern needs to apply to
40// all elements in the list, every derived Element class must provide an
41// implementation anyhow. The reason for this is explained after the below
42// example.)
43//
44// At least this implementation of Element.accept() is always the same, even
45// if it needs to be repeated in every derived Element class:
46// void accept(Visitor* pVisitor)
47// {
48// pVisitor->visit(this);
49// }
50//
51// Here's the third clunky part: The definitions of the Element base class and
52// Visitor base class must occur in the right order to avoid a circular
53// dependency. In C++, it looks like this:
54//
55// class Visitor; // forward declaration
56//
57// class ElementBase
58// {
59// public:
60// virtual void accept(Visitor* pVisitor) = 0;
61// };
62//
63// // Declare the derived element classes. Cannot provide implementation
64// // here because we don't know yet what the Visitor class looks like.
65// class ElementDerivedOne : public ElementBase
66// {
67// public:
68// void accept(Visitor* pVisitor);
69// };
70//
71// class ElementDerivedTwo : public ElementBase
72// {
73// public:
74// void accept(Visitor* pVisitor);
75// };
76//
77//
78// // Now that we have the derived element classes, we can make use of them.
79// // Technically, we could use class forwarding here too and move this
80// // declaration to after ElementBase but it makes more sense to define
81// // this here with the derived visitor classes.
82// class Visitor
83// {
84// public:
85// virtual void visit(ElementDerivedOne* pData) { };
86// virtual void visit(ElementDerivedTwo* pData) { };
87// };
88//
89//
90// // Implement the derived classes here (or declare them here and
91// // implement them somewhere else).
92// class VisitorOperationOne : public Visitor
93// {
94// public:
95// void visit(ElementDerivedOne* pData)
96// {
97// // Interested in ElementDerivedOne
98// }
99//
100// void visit(ElementDerivedTwo* pData)
101// {
102// // Also interested in ElementDerivedTwo
103// }
104// };
105//
106// class VisitorOperationTwo : public Visitor
107// {
108// public:
109// void visit(ElementDerivedTwo* pData)
110// {
111// // Only interested in ElementDerivedTwo
112// }
113// };
114//
115//
116// // Now that the Visitor classes and its derivations have been declared or
117// // defined, we can finally implement the accept() method on each derived
118// // Element class. This can only be done after the Visitor base class is
119// // declared.
120// void ElementDerivedOne::accept(Visitor* pVisitor)
121// {
122// pVisitor->visit(this);
123// }
124//
125// void ElementDerivedTwo::accept(Visitor* pVisitor)
126// {
127// pVisitor->visit(this);
128// }
129//
130// // Type of the composite element list.
131// typedef std::vector<std::unique_ptr<ElementBase>> ElementBaseList;
132//
133//
134// The above rigamarole is needed because of how polymorphism works in C++.
135// Putting the accept() method into the base class won't work because the
136// 'this' value will get interpreted as the base class and not the derived
137// class (a well-known limitation known as "class slicing" because the derived
138// class gets sliced off when determining matches in overloaded methods).
139//
140// C# is a lot more flexible in terms of declaration by using interfaces.
141// Since C# uses a two pass compilation, there is no need for forward
142// declarations or interleaved declarations. This might be made even simpler
143// in C# using reflection but that is an advanced topic that greatly obscures
144// the example.
145//
146// Using the visitor pattern looks like this:
147// 1. Create list of elements
148// 2. Create a visitor object
149// 3. For all elements, pass the visitor to each element
150//
151// Although the visitor pattern was originally defined as an object-oriented
152// approach to applying (mostly) arbitrary operations to data objects, it can
153// be argued that any mechanism that applies an operation to all or a select
154// number of elements in a container is an application of the visitor pattern,
155// even if that mechanism uses a function and not an object. For example, a
156// function that takes a list of elements and does something to each element
157// is a form of visitor pattern. In many cases, using a function is generally
158// simpler to implement although the function needs to test for the types of
159// elements it is visiting rather than letting the language overloading do
160// that automatically.
161//
162// A function is also not as sensitive to changes in the element interface.
163// If a new element type is added, the function will never know unless the
164// function is explicitly updated to know about the new type -- assuming that
165// particular function even needs to know about the new element type.
166//
167// On the other hand, if a visitor base class was updated with a new visit()
168// method for the new element class, all visitors derived from that base class
169// must be recompiled even if none of the visitors care about the new data
170// type.
171//
172// Note: The C++ Standard Template Library (STL) has an algorithm called
173// for_each(). This applies a user-defined function to every element in the
174// container passed to the for_each() function. This is a version of the
175// Visitor pattern, where the visitor is a user-supplied function that is
176// called with every element in the container. The elements have no knowledge
177// of this visitor, making for a much more elegant solution than described
178// in the C++ example above (the power of C++ templates in action!).
179//
180// The demonstration example here is a C# version of the big C++ example
181// described earlier but taking advantage of C#'s two-pass compilation to
182// eliminate the need for forward declarations.
183//
184// See the Visitor_Element_Classes.cs file for details on the element classes
185// that can accept a visitor.
186
187using System;
188using System.Collections.Generic;
189
191{
192
200 public class OrderVisitor : Visitor
201 {
205 public string[] ItemsToOrder
206 { get; set; }
207
211 public List<string> ItemsReceived
212 { get; set; }
213
218 { get; set; }
219
224 public OrderVisitor(string[] itemsToOrder)
225 {
226 ItemsToOrder = itemsToOrder;
227 ItemsReceived = new List<string>();
228 ShopNameReceivedFrom = string.Empty;
229 }
230
231 public override void VisitBaker(Visitor_Baker shop)
232 {
233 if (shop.PlaceOrder(ItemsToOrder))
234 {
237 }
238 }
239
240 public override void VisitButcher(Visitor_Butcher shop)
241 {
242 if (shop.PlaceOrder(ItemsToOrder))
243 {
246 }
247 }
248
249 public override void VisitPickleGrocer(Visitor_PickleGrocer shop)
250 {
251 if (shop.PlaceOrder(ItemsToOrder))
252 {
255 }
256 }
257
259 {
260 if (shop.PlaceOrder(ItemsToOrder))
261 {
264 }
265 }
266
268 {
269 if (shop.PlaceOrder(ItemsToOrder))
270 {
273 }
274 }
275
276 public override void VisitMaker(Visitor_Maker shop)
277 {
278 if (shop.PlaceOrder(ItemsToOrder))
279 {
282 }
283 }
284
285 public override void VisitRestaurant(Visitor_Restaurant shop)
286 {
287 if (shop.PlaceOrder(ItemsToOrder))
288 {
291 }
292 }
293 }
294}
A visitor used for ordering items from various shops. The user starts with an instance of this class ...
List< string > ItemsReceived
List of items received from an order/pickup process.
override void VisitBaker(Visitor_Baker shop)
Let the visitor visit a Visitor_Baker shop.
string[] ItemsToOrder
Items to be ordered from any shop that sells the item.
override void VisitPickleGrocer(Visitor_PickleGrocer shop)
Let the visitor visit a Visitor_PickleGrocer shop.
override void VisitButcher(Visitor_Butcher shop)
Let the visitor visit a Visitor_Butcher shop.
override void VisitCondimentGrocer(Visitor_CondimentGrocer shop)
Let the visitor visit a Visitor_CondimentGrocer shop.
override void VisitMaker(Visitor_Maker shop)
Let the visitor visit a Visitor_Maker shop.
OrderVisitor(string[] itemsToOrder)
Constructor.
string ShopNameReceivedFrom
Name of the shop that provided the item(s).
override void VisitVegetableGrocer(Visitor_VegetableGrocer shop)
Let the visitor visit a Visitor_VegetableGrocer shop.
override void VisitRestaurant(Visitor_Restaurant shop)
Let the visitor visit a Visitor_Restaurant shop.
bool PlaceOrder(string[] items)
Place an order for the specified items. If the inventory is empty, replenish the inventory by visitin...
void PickupOrder(string[] items, List< string > itemsToBePickedUp)
Pick up the items sold by this shop (assumes the items were ordered already). Basically,...
All visitors must implement this base class and then override one or more of the VisitXXX() methods,...
The namespace containing all Design Pattern Examples implemented in C#.