I have a hierarchy of classes that represents GUI controls. Something like this:
Control->ContainerControl->Form
I have to implement a series of algoritms that work with objects doing various stuff and I’m thinking that Visitor pattern would be the cleanest solution. Let take for example an algorithm which creates a Xml representaion of a hierarchy of objects. Using ‘classic’ approach I would do this:
public abstract class Control
{
public virtual XmlElement ToXML(XmlDocument document)
{
XmlElement xml = document.CreateElement(this.GetType().Name);
// Create element, fill it with attributes declared with control
return xml;
}
}
public abstract class ContainerControl : Control
{
public override XmlElement ToXML(XmlDocument document)
{
XmlElement xml = base.ToXML(document);
// Use forech to fill XmlElement with child XmlElements
return xml;
}
}
public class Form : ContainerControl
{
public override XmlElement ToXML(XmlDocument document)
{
XmlElement xml = base.ToXML(document);
// Fill remaining elements declared in Form class
return xml;
}
}
But I’m not sure how to do this with visitor pattern. This is the basic implementation:
public class ToXmlVisitor : IVisitor
{
public void Visit(Form form)
{
}
}
Since even the abstract classes help with implementation I’m not sure how to do that properly in ToXmlVisitor?
The reason that I’m considering Visitor pattern is that some algorithms will need references not available in project where the classes are implemented and there is a number of different algorithms so I’m avoiding large classes.
5
The visitor pattern is a mechanism to simulate dual binding in programming languages that only support single binding. Unfortunately, that statement might not clarify things a lot, so let me explain with a simple example.
In .NET and C#, the platform you are using, objects can be converted to strings using the ToString()
function. What that funtion does, i.e. the code being executed, depends on the type of object you’re applying it to (it’s a virtual method). What code is executed depends on one thing, the one type of the object, hence the mechanism used is called single binding.
But what if I want to have more than one way to convert an object to a string, for each different kind of object? What if I wanted to have two ways to convert objects to strings, so that the code being executed depends on two things: not only the object to be converted, but also the way in which we want it to be converted?
That could be solved nicely if we had dual binding. But most OO languages, including C#, only support single binding.
The visitor pattern solves the problem, by turning dual binding into two succesive single bindings.
In our example above, it would use a virtual method in the object to convert, that calls a second virtual method in the object implementing the conversion algorithm.
But that implies that the object upon which you want to apply the algorithm needs to collaborate with this: it needs to have support for the visitor pattern baked in.
You seem to be using .NET’s Windows Forms classes, which do not have support for the visitor pattern. More specifically, they would need to have a public virtual void Accept(IVisitor)
method, which obviously they don’t have.
So, what’s the alternative? Well, .NET does not only support single binding, it also supports dynamic binding, which is even more peowerful than dual binding.
For more information on how to apply that technique, which will allow you to solve your problem (if I understand it well), take a look at Farewell Visitor.
UPDATE:
To apply the technique to your specific problem, first define your extension method:
public static XmlDocument ToXml(this Control control)
{
XmlDocument xml = new XmlDocument();
XmlElement root = xml.CreateElement(control.GetType().Name);
xml.AppendChild(root);
Visit(control, xml, root);
return xml;
}
Create the dynamic dispatcher:
private static void Visit(Control control, XmlDocument xml, XmlElement root)
{
dynamic dynamicControl = control; //notice the 'dynamic' type.
//this is the key to dynamic dispatch
VisitCore(dynamicControl, xml, root);
}
Then fill in the specific methods:
private static void VisitCore(Control control, XmlDocument xml, XmlElement root)
{
// TODO: specific Control handling
}
private static void VisitCore(ContainerControl control, XmlDocument xml, XmlElement root)
{
// call the "base" method
VisitCore(control as Control, xml, root);
// TODO: specific ContainerControl handling
// for example:
foreach (Control child in control.Controls)
{
XmlElement element = xml.CreateElement(child.GetType().Name);
root.AppendChild(element);
// call the dynamic dispatcher method
Visit(child, xml, element);
}
}
private static void VisitCore(Form control, XmlDocument xml, XmlElement root)
{
// call the "base" method
VisitCore(control as ContainerControl, xml, root);
// TODO: specific Form handling
}
4