Level: Intermediate to Object Oriented Programming; Beginner
+ with .NET and C#
The visitor design pattern is very useful in situations in
which normal polymorphism will not work because we have fundamentally different
objects, with different interfaces, that you want to work on your concrete main
object. The pattern is used to give us a way to do these different operations
on the object. According to the GOF book, you use the visitor pattern when:
1. An object structure contains many classes of objects with
differing interfaces and you want to perform operations on these objects that
depend on their concrete classes.
2. Many distinct and unrelated operations need to be performed on objects in an
object structure and you want to avoid "polluting" their classes with
these operations. Visitor lets you keep related operations together by
defining them in one class. When the object structure is shared by many
applications, use Visitor to put operations in just those applications that
need them.
3. The classes defining the object structure rarely change, but you often want
to define new operations over the structure. Changing the object structure
classes requires redefining the interface to all visitors, which is potentially
costly. If the object structure classes change often, then it is probably
better to define the operations in those classes (GOF333).
We first start by defining a base class for our visitor
object collection. We will use our common Cbulldog object for our example.
Listing 9
public class BaseVisitor
{
public BaseVisitor(){}
virtual public void VisitBulldog(CBulldogdog){}
}
The code is pretty simple, having only a single method that
takes an instance of the bulldog to work on. We can then derive concrete
instances of visitors from this base. We will derive two for our example, one
that changes our dog’s age and another that changes his bark. First we will look
at our Cbulldog class. It has not changed much; there is only one new method.
Listing 10
public class CBulldog
{
private int _Age;
private string _BarkString;
public CBulldog()
{
_Age = 0;
_BarkString = "Woof";
}
public void TakeVisitor(BaseVisitor bv)
{
bv.VisitBulldog(this);
}
public void Bark()
{
Console.WriteLine(_BarkString);
}
public int MyAge
{
get
{
return _Age;
}
set
{
_Age = value;
}
}
public string MyBark
{
get
{
return _BarkString;
}
set
{
_BarkString = value;
}
}
}
The TakeVisitor method allows our class to accept any
visitor that is derived from BaseVisitor. The method simply calls the
VisitBulldog method and passes a pointer to the current class.
Now we look at our concrete derived visitor classes. First,
the class that will change the dog’s age:
Listing 11
public class AgeVisitor: BaseVisitor
{
public AgeVisitor(): base(){}
public override void VisitBulldog(CBulldogdog)
{
dog.MyAge = 5;
}
}
Listing 12 is the class that will change his bark.
Listing 12
public class BarkVisitor: BaseVisitor
{
public BarkVisitor(): base(){}
public override void VisitBulldog(CBulldogdog)
{
dog.MyBark = "Yip Yip";
}
}
As you see, all that the methods do are to act upon the dog
object passed to them. You can make them as complex or simple as needed.
Our test code for the example:
Listing 13
CBulldog Mydog;
Mydog = new CBulldog();
Mydog.Bark();
Console.WriteLine(Mydog.MyAge.ToString());
AgeVisitor av;
av = new AgeVisitor();
Mydog.TakeVisitor(av);
Console.WriteLine(Mydog.MyAge.ToString());
BarkVisitor bv;
bv = new BarkVisitor();
Mydog.TakeVisitor(bv);
Mydog.Bark();
If you watch the output window, you will see that the calls
to TakeVisitor change the dog’s information.
This pattern can help keep your objects cleaner when there
are many different types of visitors you want to use.