The Visitor and Visitable classes allow an
author to implement the Visitor design pattern. This pattern can be used when
you need to encapsulate an operation that you want to perform on the elements
of a data structure, and allows you to change the operation being performed on
a structure without having to change the classes of the elements you’re
operating on.
The Visitor pattern decouples
classes from the algorithms used on them. It eliminates the need for switch statements based on the class of an object.
The VisitorIF interface requires a Visitor
class object to define visitVisitableClass(visitable) methods that
contain the algorithm you wish to have performed on the Visitable class object.
The VisitableIF interface requires a Visitable
class object to define accept(visitor) methods that should call the appropriate
visitor.visitVisitableClass(visitable) method.
Visitor is a base class for deriving all visitor objects. It
defines a visitVisitable() method, which by default does nothing.
Visitable is a mix-in class that should be the last superclass defined
on an object that you wish to make visitable. This class defines an accept()
method that uses reflection to determine the appropriate visitVisitableClass() method on the visitor, and passes self as an argument.
By passing the appropriate
visitor to an object’s accept method, we determine what kind of algorithm is
going to be performed. The accept() method then determines which vistXXXX
method to call based on the object’s class structure, and then calls that
method on the visitor, passing itself as the method argument.
Suppose we simulate the
simple Rock, paper, scissors game. There are a number of ways to do this, but
with the visitor pattern we don’t need to implement switches.
First we’ll define our Visitable classes. The GameVisitable is abstract, and defines only one method beats(). This method takes one argument other. The syntax will then would be something like rock.beats(scissors) and the method should return true or nil, depending
on the simple rules of rock, paper, scissors.
class GameVisitable:
Visitable
{
beats(other)
{
local v = other.getBeatsVisitor();
return accept(v);
}
}
The beats() method first asks the other object to produce a visitor, and then passes that visitor to
its accept() method.
Now we’ll define the concrete
classes for rock, paper, scissors.
class Rock: GameVisitable
{
getBeatsVisitor() {return new
BeatsRockVisitor();}
}
class Paper: GameVisitable
{
getBeatsVisitor() {return new
BeatsPaperVisitor();}
}
class Scissors:
GameVisitable
{
getBeatsVisitor() {return new
BeatsScissorsVisitor();}
}
Notice that these classes
only define the getVisitor() method required by beats().
The accept() method, defined by Visitable,
will determine which property to call on the visitor by using reflection. Unlike a switch statement, it never needs modifying since the
property is automatically constructed and retrieved from the global symbols
table.
Now we define our visitors. BeatsVisitor is an abstract class that simplifies the coding of
our other visitors by allowing us to inherit default values.
class BeatsVisitor: Visitor
{
visitRock(visitable) {return nil;}
visitPaper(visitable) {return nil;}
visitScissors(visitable) {return nil;}
}
And now we can define our
concrete visitor classes:
class BeatsRockVisitor:
BeatsVisitor
{
visitPaper(visitable) {return true;}
}
class BeatsPaperVisitor:
BeatsVisitor
{
visitScissors(visitable) {return true;}
}
class BeatsScissorsVisitor:
BeatsVisitor
{
visitRock(visitable) {return true;}
}
Notice that we only have to
define the cases where our visitable class (i.e. visitXXXX) wins. So in our BeatsRockVisitor, the visitPaper() method
returns true, because paper, our visitable,
beats Rock, our visitor.
Now we simply produce
instances of rock, paper, and scissors then put it to the test:
local v, rock, paper, scissors;
rock =
new Rock();
paper =
new Paper();
scissors =
new Scissors();
v = rock.beats(rock);
v = rock.beats(paper);
v = rock.beats(scissors);
v = paper.beats(rock);
v = paper.beats(paper);
v = paper.beats(scissors);
v = scissors.beats(rock);
v = scissors.beats(paper);
v = scissors.beats(scissors);
If we want to add a new
GameVisitable, we need only add the new Visitable and Visitor objects, and
modify the appropriate visitors.
This file is part of the TADS 3
Proteus Library Extension
Copyright ©
2001-2004 Kevin Forchione. All rights reserved.