Tuesday, April 15, 2008

Danger: Visitor Design Pattern can be useful

In seems that in my circles that out of all the design patterns in the gang of four, the Visitor pattern is often seen as confusing and impractical. I'd agree with that assessment: patterns like the Command, Strategy, Composite, and Factory are commonly used because it's easy to think of examples that work. Whereas the Visitor Pattern has a confusing relationship between objects and requires a lot of upfront code to make it work. It's easily filed under the i-don't-think-i'll-ever-use-this category.

I recently found a great code example on Haibo Luo's blog that involved using reflection to read IL (using Method.GetMethodBodyAsIL()). In it he posts two very different approaches to parsing the IL, the first post shows a Reflector-like example of a IL-Reader; the second post is focused on a related side-project but outlines how he was able to use the Visitor pattern to allow different interpretations of the IL. The Visitor pattern is perfect here, because IL is based on a fixed specification that will never change. (A side note: the entire ILReader class is attached as a zip at the bottom of the post and is worth checking out if you're interested in parsing IL using Reflection.)

After showing this example to a few colleagues (with some heated debates), I found new appreciation for the Visitor pattern. The Visitor Pattern can be really useful anywhere you have a fixed set of data, which surprisingly happens more frequently than you might think.

Take "Application Configuration" as an example. Normally, I'd write a simple Parser to read through the configuration to construct application state. Since the configuration elements are a fixed object model, they can be easily modified to accept a visit from a visitor:

public interface IConfigVisitor
{
void Visit(MyConfig configSection);
void Visit(Type1 dataElement);
void Visit(Type2 dataElement);
}

public interface IConfigVisitorAcceptor
{
void Accept(IConfigVisitor visitor);
}

public class MyConfig : ConfigurationSection, IConfigVisitorAcceptor
{
// config stuff here, omitted

public void Accept(IConfigVistior visitor)
{
visitor.Accept(this);
}
}

public class Type1 : ConfigurationElement, IConfigVisitorAcceptor
{
// config stuff here, omitted

// example fields for Type1
public string Field1;

public void Accept(IConfigVisitor visitor)
{
visitor.Visit(this);
}
}

public class Type2 : ConfigurationElement, IConfigVisitorAcceptor
{
// config stuff here, omitted

// example fields for Type2
public string Field1;
public string Field2;

public void Accept(IConfigVisitor visitor)
{
visitor.Visit(this);
}
}

Little modification needs to be done to the parser to act as a Visitor. The parser is simply a visitor that collects state as it travels to each configuration element. This example is a bit trivial:


public class ConfigurationParserVisitor : IConfigVisitor
{
// example internal state for visitor
StringBuilder example = new StringBuilder();

public void Visit(MyConfig configSection)
{
// a custom iterator could be used here to simplify this
foreach(Type1 item in configSection.Type1Collection)
{
item.Accept(this);
}
foreach(Type2 item in configSection.Type2Collection)
{
item.Accept(this);
}
}

public void Visit(Type1 data)
{
example.AppendLine(data.Field1);
}
public void Visit(Type2 data)
{
example.AppendLine(data.Field1 + " " + data.Field2);
}

public string GetOutput()
{
return example.ToString();
}
}

public class Example
{
public static void Main()
{
MyConfig config = (MyConfig)ConfigurationManager.GetSection("myconfig");

ConfigurationParserVisitor parser = new ConfigurationParserVisitor();
config.Accept(parser);

Console.WriteLine(parser.GetOutput());
}
}

Here's usually where the argument gets heated: Why would anyone do this? Wouldn't you be better off writing a parser that accepts your configuration element as a parameter? A very valid question, it does seem an obtuse direction to follow if you only need to read your configuration file. However, where the Visitor pattern becomes useful is that new functionality can be added to the configuration elements without having to modify the object model in any way. Perhaps you want to auto-upgrade your settings to a new version, produce a report, display your configuration in a UI, etc.

One of the subtle advantages to this pattern is that new functionality can be expressed in a single class rather than spread about the solution. This makes it perfect fit for adding plugins to your application, or building an application that is composited together with a Command pattern.

While not all application will require this level of flexibility, it can be a very useful pattern when you need it. The upfront cost is a one-time event, so it's a pretty easy refactoring exercise.

submit to reddit

0 comments: