Visitor

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 Visitor Interface

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 Visitable Interface

The VisitableIF interface requires a Visitable class object to define accept(visitor) methods that should call the appropriate visitor.visitVisitableClass(visitable) method.

 

Visitor

Visitor is a base class for deriving all visitor objects. It defines a visitVisitable() method, which by default does nothing.

 

Visitable

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.

 

How It Works

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.

 

An Example: Rock, Paper, Scissors

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.