Code Design
Courses and Workshops on Effective Code Design
Post-image

The Visitor Pattern

I have recently given my Design Patterns Explained training, and it felt like the Visitor Pattern discussion created the most aha moments in the audience. It seems that people have a hard time getting this pattern, so I thought to explain my understanding of it in the post.

The Gang of Four book says:

Represents an operation to be performed on the elements of an object structure.

Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Go back and read the last sentence once more. Quite an ambitions desire to add operations without modifying the classes, isn't it?

Lets see what this actually means. I like to explain this pattern using the following example:

Say we have some classes that represent commands:

class PurchaseOrderCommand
{
	public Product Product { get; set;}
	public int Quantity { get; set;}
}

class SalesOrderCommand
{
	public IEnumerable<OrderLine> OrderLines { get; set;}
	public string CustomerCode { get; set; }
	public DateTime Date { get; set; }
}

class CustomerCommand
{
	public string Name { get; set; }
	public string BusinessDomain { get; set; }
}

and some client code which keeps these in a data structure, a List lets say:

public class CommandsManager // refered as the Client code
{
	List commands = new List();

	// The client class has a structure (a list in this case) of the items (commands).
	// The client knows how to iterate through the structure
	// The client would need to do different operations on the items from the structure when iterating it
}

(List is one of the simplest data structure we use. Even if the Visitor Pattern addresses the cases when the data structure is complex and difficult to iterate, for the simplicity of this example, I use only a list.)

Now, lets add some operations with these items. Say we want to add:

  • PrettyPrint() - a function that will produce a nice report with all the commands which are pending
  • Approve() - a function that will approve each command for execution
  • Save() - a function that will persist all the commands, so we don't loose them when the application is restated

These operations belong to different areas of concern. The PrettyPrint() would have presentation concerns, the Approve() would have business logic concerns and the Save() would have data access concerns.

One way to do these operations on all the items we have, is to add these functions to the client code, the CommandsManager class in our example. The result would be:

and the code would be:

public class CommandsManager
{
	readonly List<object> items = new List<object>();

	public void PrettyPrint()
	{
		foreach (var item in items)
		{
			if (item is PurchaseOrderCommand)
			   Print((PurchaseOrderCommand)item);
			else if (item is SalesOrderCommand)
				Print((SalesOrderCommand)item);
			else if (item is CustomerCommand)
				Print((CustomerCommand)item);
		}
	}

	private void Print(PurchaseOrderCommand item)
	{
		Console.WriteLine($"Purchase order command: Product={item.Product} Quatity={item.Quantity}");
    }

	private void Print(SalesOrderCommand item)
	{
		Console.WriteLine("Sales order command: ");
		foreach (var line in item.OrderLines)
		{
			Console.WriteLine($"\t Product={line.Product} Quantity={line.Quantity}");
		}
	}

	private void Print(CustomerCommand item)
	{
		Console.WriteLine($"New customer command: {item.Name} in business: {item.BusinessDomain}");
	}

	public void ApproveAll()
	{
		foreach (var item in items)
		{
			if (item is PurchaseOrderCommand)
				Approve((PurchaseOrderCommand)item);
			else if (item is SalesOrderCommand)
				Approve((SalesOrderCommand)item);
			else if (item is CustomerCommand)
				Approve((CustomerCommand)item);
		}
	}

	private void Approve(CustomerCommand item)
	{
		// Interact w/ the databse and use external services to process a new purchase order command
	}

	private void Approve(SalesOrderCommand item)
	{
		// Interact w/ the databse and use external services to process a new purchase order command
	}

	private void Approve(PurchaseOrderCommand item)
	{
		// Interact w/ the databse and use external services to process a new purchase order command
	}

	public void Save()
	{
		// This might mix DA concerns w/ UI concerns
	}
}

This approach is not a good solution in most of the contexts. The separation of concerns is poor and the costs of change will be high. Each time a new type of command will appear we need to change this client class. Even more, changes in the presentation logic may affect the Approve() or the Save() code, because all these are implemented in same class for all our commands.

Another way is to add these operations to each of the command classes. This would look like:

and the code like:

public class CommandsManager
{
	readonly List<ICommand> commands = new List<ICommand>();

	public void ApproveAll()
	{
		foreach (var item in commands)
		{
			item.Approve();
		}
	}

	public void PrettyPrint()
	{
		foreach (var item in commands)
		{
			item.PrettyPrint();
		}
	}
}

class PurchaseOrderCommand : ICommand
{
	public void Approve()
	{
		// Interact w/ the databse and use external services to process a new purchase order command
	}

	public void PrettyPrint()
	{
		Console.WriteLine($"Purchase order command: Product={Product} Quatity={Quantity}");
	}

	public Product Product { get; }
	public int Quantity { get;  }
}
...

Here the code that iterates through the data structure (the list of commands) is isolated from the code that implements the commands. This is an advantage from the previous approach. However, now each time we need to add a new operation we will need to change all the existent command classes to add that new operation. This may induce a high cost of change for two reasons: first, we still have a poor separation of concerns (one command class has presentation code, mixed with business logic code and mixed with data access code); second, changing the interface of these classes will trigger changes in all the other classes that use them.

So, can we come with a better design than these two approaches? Yes, if we apply the Visitor Pattern.

We apply it by evolving our previous designs. We define two interfaces: IVisitable and IVisitor. The class diagram is now like this:

Lets go through the code as and see how they interact.

The IVisitable interface is implemented by all the items in the data structure (by all the command classes in our example). The operations we wanted are not functions of this interface, because the operations may vary. This interface has only one function that will accept a visitor.

public interface IVisitable
{
	void Accept(IVisitor visitor);
}

By implementing this, each item from the data structure, will pass itself to the visitor and let the visitor to implement whatever operation it wants on it.

The implementation is trivial. It just calls the appropriate VisitXXX() method of the visitor.

public class PurchaseOrderCommand : IVisitable
{
	public void Accept(IVisitor visitor)
	{
		visitor.VisitPurchaseOrderCommand(this);
	}
	...
}

public class SalesOrderCommand : IVisitable
{
	public void Accept(IVisitor visitor)
	{
		visitor.VisitSalesOrderCommand(this);
	}
	...
}

public class CustomerCommand : IVisitable
{
	public void Accept(IVisitor visitor)
	{
		visitor.VisitCustomerCommand(this);
	}
	...
}

The client code (CommandsManager) iterates through the data structure, and for each item it calls the Accept(IVisitor) method, passing the visitor that visits that item.

public class CommandsManager
{
	private readonly List<IVisitable> items = new List<IVisitable>();

	public void PrettyPrint()
	{
		ReportVisitor report = new ReportVisitor();
		foreach (var item in items)
		{
			item.Accept(report);
		}

		report.Print();
	}
        ...
}

Now, the client code does one thing only: it iterates through the data structure. The implementation of any operation on the data structure or on its elements is forwarded to the visitor classes.

The visitors are classes that focus on the behavior only and on one operation only. They implement the operations we want. When we need a new operation we will create a new visitor and so on. For example the ReportVisitor creates a printable report with all the commands. It is used to implement the PrettyPrint() operation.

class ReportVisitor : IVisitor
{
	public void VisitCustomerCommand(CustomerCommand customerCommand)
	{
		report.AppendLine($"VisitCustomerCommand customer command: {customerCommand.Name} in business: {customerCommand.BusinessDomain}");
	}

	public void VisitSalesOrderCommand(SalesOrderCommand salesOrderCommand)
	{
		report.AppendLine("Sales order command: ");
		foreach (var line in salesOrderCommand.OrderLines)
		{
			report.AppendLine($"\t Product={line.Product} Quantity={line.Quantity}");
		}
	}

	public void VisitPurchaseOrderCommand(PurchaseOrderCommand purchaseOrder)
	{
		report.AppendLine($"Purchase order command: Product={purchaseOrder.Product} Quatity={purchaseOrder.Quantity}");
	}

	public void Print()
	{
		Console.WriteLine(report);
	}
}

We can design the visitors as we want. We may have one visitor that does one operation (the printing) for all the items (commands) as the above, or we can have one visitor for one operation for one type of item, as in the following example.

class PurchaseOrderCommandApprover : IVisitor
{
	public void VisitPurchaseOrderCommand(PurchaseOrderCommand purchaseOrder)
	{
		// code that approves the command of creating a new purchase order.
		// this code may use external classes or services to the approval
	}
	public void VisitCustomerCommand(CustomerCommand customerCommand)
	{	// we do nothing here because we only deal with new purchase orders approval
	}

	public void VisitSalesOrderCommand(SalesOrderCommand salesOrderCommand)
	{	// we do nothing here because we only deal with new purchase orders approval
	}
}

class CustomerCommandApprover : IVisitor
{
	private ICrmService crmService;
	public CustomerCommandApprover(ICrmService crmService)
	{
		this.crmService = crmService;
	}

	public void VisitCustomerCommand(CustomerCommand customerCommand)
	{
		// code that approves the command of creating a new customer.
		// uses the ICrmService to do the approval
	}

	public void VisitSalesOrderCommand(SalesOrderCommand salesOrderCommand)
	{	// we do nothing here because we only deal with new purchase orders approval
	}

	public void VisitPurchaseOrderCommand(PurchaseOrderCommand purchaseOrder)
	{	// we do nothing here because we only deal with new purchase orders approval
	}
}

The way we design the visitors depends on the operation we implement. For the ReportVisitor we wanted a report with all the types of the commands, and it made sense to have code when any element from the structure was visited. For the Approve() operation is the other way around. Here, because we have a very different logic for approving a purchase order command from approving a new customer command, we need a better separation. We created one visitor class for the approval of each element type. It only has code on the VisitXXX() method that corresponds to the element type it is interested in. These classes may have different dependencies the CustomerCommandApprover needs a the ICrmService and the others will not. Also, they will change and evolve separately, so it makes sense to make this kind of separation.

By applying the Visitor Pattern we have achieved a design with a good separation of concerns. Now the client code that iterates the data structure (CommandManager), is separated from the implementation of the operations. The items (the command classes) only hold their specific data and do not have behavior. The operations are implemented by the visitors and we can easily define and add new of them.

The Visitor Pattern brings most value when applied in the contexts where the data structure is complex (a tree or a graph), the types of items it holds (the nodes) are quite fixed and the operations we want on those items vary a lot. Good examples of these are the syntax trees, which are source code representation in a tree data structure. Here the programming language grammar is fixed, which means that the nodes in the tree are fixed. After the syntax tree is created by parsing the code, then we can define different visitors which will visit each node and we can add any operation on the code. This is useful for doing static code analyses, generating code, printing code etc.

A good example of Visitor implementation in .NET Framework is the ExpressionVisitor class. We can inherit from it to implement different operations on a lambda expression. I have used it many times to parse LINQ queries to alter them before they are sent further.

If we go back to our initial example we can make another important observation: the Visitor Pattern helps us to follow the principle that says "Separate Data from Behavior". A principle that I've first read in the Clean Code book. We have started with our command classes which were only data. Data that represents a command. First we wanted to keep them clean of the behavior and we added the operations in the client code, resulting a switch on the object type. Not an OOP design. Then we moved the behavior into the command classes. This was not separating it from data, and we've seen the drawbacks. In the end by applying the Visitor Pattern we have managed to get to a good separation and to get a reduced cost of change as pursued by the Separate Data from Behavior principle. I won't go in more details about this principle here, even if it would turn into an interesting discussion. I'll do it in a future post.

I believe that the Visitor Pattern is not too complex and if used in the correct context it can lead to a better design, a design that embraces change. I hope that the example that I've described here gives an useful addition to all the other good writings on this pattern. The entire source code, including a running demo is available on Github here as part of my Design Patterns Explained course.

Many more patterns are explained with examples in my Design Patterns Explained course
Featured image credit: FWSAM via 123RF Stock Photo

You've successfully subscribed to Code Design
Great! Next, complete checkout to get full access to all premium content.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Error! Billing info update failed.