Diagram of the visitor pattern
The Visitor pattern is a way of separating data from operations where the operations can be applied after the list of data is created all while leaving the data and operations as objects. The visitor pattern works best where the number of data classes changes very infrequently. This is because the data classes and associated Visitor class must be engineered to support the Visitor pattern.
The Visitor pattern is a way to use operations on or with a collection of data classes without changing the data classes themselves. The data classes are set up to support a visitor class and from then on can support any visitor that derives from that visitor class.
All Visitor classes derive from a known Visitor base class provided by the data class implementation. This is a consequence of the fact that the data class must support visitors so it must dictate the interface to the visitors.
The data can be organized in a flat list or in a hierarchical tree. The root of the list or tree is responsible for making sure a given visitor instance visits all the elements. At the core of the Visitor pattern are the following two methods.
The data classes implement an "accept" method like the following (the name "accept" is convention only):
class SomeDataType : public ISupportVisitors
{
public:
void accept(Visitor visitor)
{
visitor.VisitSomeDataType(this);
}
};
All data elements, including the root of the data list or tree, inherits from the ISupportVisitors interface, which declares the accept()
method and therefore must be implemented in every data class. The idea is for the visitor to be given the concrete class representing a specific data type, so the visitor can access the data type.
The Visitor implements one or more "visit" methods, one for each data class:
class SomeVisitor : public VisitorBase
{
public:
void visitSomeDataType(SomeDataType data)
{
}
};
The key to making this work is for all visitors to inherit from one base class where each of the VisitXXX() methods (one for each data type) does nothing but can be overridden in the derived class to take some action. Here is an example base class for all visitors:
class VisitorBase
{
public:
virtual void visitSomeDataType(SomeDataType* data) {}
virtual void visitSomeOtherDataType(SomeOtherDataType* data) {}
virtual void visitSomeThirdDataType(SomeThirdDataType* data) {}
};
The derived Visitor class would override only the method or methods they are interested in.
Note: It is possible to use visit(SomeDataType* data()
, visit(SomeOtherDataType* data)()
, etc., if the programming language being used supports overloaded methods. Which approach to use is a matter of style and preference.
Adding new data types requires adding a new VisitXXX() method to the Visitor base class; this is why the recommendation to use the Visitor pattern only when the data types are going to change infrequently.
How to Use
The example provided here shows a C++, a C#, Python, and C version of the same visitor pattern. However, the C version is all functions and structures.
For C, the entry point for visiting shops is Village_VisitShop(), which loops over all shops and calls OrderVisitor_VisitShop() with each shop in turn. There are no structures representing types of shops so the Visitor_Shop structure represents all shops. OrderVisitor_VisitShop() ignores any store that doesn't sell the requested items.
C++
void Visitor_Exercise()
{
std::cout << std::endl;
std::cout << "Visitor Exercise" << std::endl;
std::cout << " Creating Village" << std::endl;
std::unique_ptr<Visitor_Village> village = std::make_unique<Visitor_Village>();
village->LoadVillage();
std::cout
village->Name.c_str())
<< std::endl;
village->Accept(&visitor);
if (!visitor.ItemsReceived.empty())
{
std::cout
visitor.ItemsReceived[0].c_str(),
visitor.ShopNameReceivedFrom.c_str())
<< std::endl;
}
else
{
std::cout << " Failed to receive a hamburger" << std::endl;
}
std::cout << " Done." << std::endl;
}
std::vector< std::string > StringList
Typedef for a vector of std::string.
std::string formatstring(const char *fmt,...)
Use the given string and arguments to return a buffer containing the formatted string....
Represents a visitor used for ordering items from various shops. The user starts with an instance of ...
C#
public void Run()
{
Console.WriteLine();
Console.WriteLine("Visitor Exercise");
Console.WriteLine(" Creating Village");
village.LoadVillage();
Console.WriteLine(
" Ordering a hamburger from a shop in the {0}", village.
Name);
village.Accept(visitor);
{
Console.WriteLine(" We received a {0} from {1}.",
}
else
{
Console.WriteLine(" Failed to receive a hamburger");
}
Console.WriteLine(" Done.");
}
ConstStringList ItemsReceived
List of items received from an order/pickup process.
const char * ShopNameReceivedFrom
Name of the shop that provided the item(s). Borrowed pointer.
Represents a collection of shops that can be visited.
const char * Name
Name of the village.
Python
def Visitor_Exercise():
print()
print("Visitor Exercise")
print(" Creating Village")
village = Visitor_Village()
village.LoadVillage()
print(" Ordering a hamburger from a shop in the {0}".format(village.Name))
village.Accept(visitor)
if visitor.ItemsReceived:
print(" We received a {0} from {1}.".format(visitor.ItemsReceived[0], visitor.ShopNameReceivedFrom))
else:
print(" Failed to receive a {0}".format(visitor.ItemsToOrder[0]))
print(" Done.")
C
void Visitor_Exercise(void)
{
printf("\nVisitor Exercise\n");
printf(" Creating Village\n");
if (success)
{
if (success)
{
printf(
" Ordering a hamburger from a shop in the %s\n", village.
Name);
if (success)
{
{
printf(" We received a %s from %s.\n",
}
else
{
printf(" Failed to receive a hamburger\n");
}
}
}
else
{
printf(" Error! Out of memory condition adding item to list of things to order!\n");
}
}
else
{
printf(" Error! Out of memory loading the village!\n");
}
printf(" Done.\n");
}
void OrderVisitor_Initialize(OrderVisitor *visitor)
Initialize the specified OrderVisitor object.
void OrderVisitor_Clear(OrderVisitor *visitor)
Clear the specified OrderVisitor object, freeing up any memory that it was using. The resulting objec...
void Village_Clear(Village *village)
Clear the specified Village object, releasing any allocated memory associated with the village and it...
bool Village_VisitShop(Village *village, OrderVisitor *visitor)
Visit all shops in the given Village object to find the ingredients specified in the OrderVisitor obj...
bool Village_Load(Village *village)
Set up the specified Village object with all the shops that can be visited.
void Village_Initialize(Village *village)
Initialize the specified Village object.
bool ConstStringList_AddString(ConstStringList *stringList, const char *string)
Add a string to the given string list.
size_t strings_count
Number of strings in the strings list.
const char ** strings
Pointer to an array of zero-terminated string pointers. These strings are constant and will not be du...
ConstStringList ItemsToOrder
Items to be ordered from any shop that sells the item.
RUST
(Apologies. Doxygen does not understand Rust syntax and therefore cannot colorize the code.)
pub fn visitor_exercise() -> Result<(), String> {
println!("");
println!("Visitor Exercise");
let mut village = Village::new();
village.load();
let mut order = OrderVisitor::new(&vec!["hamburger".to_string()]);
println!(" Ordering a hamburger from a shop in the {0}", village.name);
village.visit(&mut order);
if !order.items_received.is_empty() {
// We are expecting only a single item
println!(" We received a {0} from {1}.",
order.items_received[0], order.shop_name_received_from);
} else {
println!(" Failed to receive a hamburger");
}
println!(" Done.");
Ok(())
}
See Also