views:

392

answers:

5

Based on the code I've found, it seems that the Visitor is required to known the structure of the visited objects and call on the needed children. This seems a bit clunky in some cases where the visitor would want to continue to work even if the visited classes are modified.

I guess the real question is: Is their a pattern where the enumeration is done by the visited code rather than the visitor code?

A: 

Have a look at the explanation in this article.

From Wiki:

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure upon which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. Thus, using the visitor pattern helps conformance with the open/closed principle.

Mitch Wheat
What I'm really looking for is the justification for the /form/ of the pattern not is /existence/.
BCS
+4  A: 

The visitor object is required to know the structure of the things it visits. That's OK, though. You're supposed to write specialized visit operations for each type of thing the visitor knows how to visit. This allows the visitor to decide how much it really wants to visit, and in what order.

Suppose you have a tree. One visitor might do a pre-order traversal, one might do an in-order traversal, and yet another visitor might act only on leaf nodes. The visitor classes can do all these things without requiring any changes to the tree class.

The visitor knows the structure, but that doesn't necessarily mean the operation the visitor performs knows all the structure. You might combine a visitor with a command. Give the visitor object a command object, and the visitor will invoke the command on each thing it visits.

If you want to have a simple operation and let the collection give you each item to act on, then you want the collection to provide an iterator for itself. Call your function on each thing the iterator gives you.

If you want to iterate over the tree's nodes in various orders, then the tree will need to offer multiple iterators. If you want to process nodes in an order that the tree doesn't already support, you'll need to modify the tree class.

Rob Kennedy
Very nice answer. I agree -- if there is 1 (or a small number of) ways of enumerating items in the collection, forget visitors and just provide STL-style iterators. Otherwise (e.g. A* search) use a visitor to provide the "framework," and separate out the "per-item work" into a Command if possible.
j_random_hacker
+1  A: 

In short words, I think that Visitor pattern is orthogonal to the way enumeration is done. It can be done either way, or no enumeration at all.

I think that visitor is required to know what elements visited structure consists of. Like to know that car consists of Wheels and Engine. To know how exactly they are combined is not necessary, I think. Consider following example. Insider knows visited object structure and performs enumeration itself. Outsider does not know it and delegates enumeration to the visited object.

interface Visitable {
    void accept(Visitor visitor);
}

class WorkingRoom implements Visitable {
    public int number;
    WorkingRoom(int number) {
        this.number = number;
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class BossRoom implements Visitable {
    public String bossName;
    BossRoom(String bossName) {
        this.bossName = bossName;
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

interface Visitor{
    void visit(WorkingRoom workingRoom);
    void visit(BossRoom bossRoom);
    void visit(Office office);
}

class Office implements Visitable{
    public Visitable[] firstFloor;
    public Visitable[] secondFloor;
    public Visitable ceoRoom;
    public Office(){
        firstFloor = new Visitable[]{ new WorkingRoom(101),
                                        new WorkingRoom(102),
                                        new BossRoom("Jeff Atwood"),
                                        new WorkingRoom(103)};
        secondFloor = new Visitable[]{  new WorkingRoom(201),
                                        new WorkingRoom(202),
                                        new BossRoom("Joel Spolsky")};

        ceoRoom = new BossRoom("Bill Gates");
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void showMeTheOffice(Visitor visitor, boolean sayPlease) {
        // Office manager decides the order in which rooms are visited
        for(int i=secondFloor.length-1; i >= 0; i--){
            secondFloor[i].accept(visitor);
        }
        if (sayPlease){
            ceoRoom.accept(visitor);
        }
        for (int i = 0; i < firstFloor.length; i++) {
            firstFloor[i].accept(visitor);
        }
    }
}

class Insider implements Visitor{
    public void visit(WorkingRoom workingRoom) {
        System.out.println("I> This is working room #"+workingRoom.number);
    }

    public void visit(BossRoom bossRoom) {
        System.out.println("I> Hi, "+bossRoom.bossName);
    }

    public void visit(Office office) {
        // I know about office structure, so I'll just go to the 1st floor
        for(int i=0;i<office.firstFloor.length;i++){
            office.firstFloor[i].accept(this);
        }
    }
}

class Outsider implements Visitor{

    public void visit(Office office) {
        // I do not know about office structure, but I know they have a 
        // nice office manager
        // I'll just ask to show me the office
        office.showMeTheOffice(this, true);
    }

    public void visit(WorkingRoom workingRoom) {
        System.out.println("O> Wow, room #"+workingRoom.number);
    }

    public void visit(BossRoom bossRoom) {
        System.out.println("O> Oh, look, this is "+bossRoom.bossName);
    }
}

public class Main{
    public static void main(String[] args) {
        Office office = new Office(); // visited structure
        // visitor who knows about office structure
        Insider employee = new Insider(); 
        office.accept(employee);
        System.out.println();
        // visitor who does not know about exact office structure
        // but knows something else
        Outsider candidate = new Outsider(); 
        office.accept(candidate);

        // no enumeration at all, but still a visitor pattern
        Visitable v = new BossRoom("Linus Torvalds");
        v.accept(candidate);
    }
}

I had a project with wide usage of visitor pattern without any enumeration at all. We had base interface Field and many classes implementing it, like StringField, NumberField, etc. Very often we had to do different things based on the field type, for example render it in a different way, load from DB, export to xml, etc. We could define methods in Field interface, but that would make it coupled with every single feature of the project - poor field has to know about exporting, importing, rendering to html and rtf, etc. We also could use instanceof but set of possible classes implementing Field interface was changed over time and it was possible to add new field type and forget to add

else if (field instanceof NewlyAddedFieldType) {...}

somewhere. So we decided to use visitor pattern, and it was like

Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);

As any Field implementation is required to have method

void accept(FieldVisitor visitor)

then if I add new implementation of Field interface, I have to implement it somehow. Normally it is

visitor.visit(this);

where this is a newly added class. This forces me to add

void visit(NewlyAddedClass visited);

to FieldVisitor interface, which makes me implement it in every FieldVisitor implementation that we already have. So if I forget to do something of this - I'll get compiler error. Enumeration in this case, if any, was done outside of visited structure and visitor. But I still think about it as a valid case of visitor pattern. It happend to be a little bit harder to implement, but easier and safer to use.

Pavel Feldman
+1  A: 

Yes. The visited objects can do the enumeration (i.e. call on the needed children). This is still called the "Visitor" pattern (in fact, Design Pattern's first sample of Visitor does it this way). My made-up example snippet:

public void accept(Visitor visitor) {
  for (Node n : children) {
    n.accept(visitor);
  }
}

Note: for visiting the children, we can't say visitor.visit(n);. This is because Java does not dynamically select the method (based on the runtime class of its arguments), but selects the method statically (by the compile-time type of its arguments).

13ren
A: 

The Hierarchical Visitor Pattern explains a different approach where it adds events for entering and leaving levels. The associated discussion page presents arguments for iteration being within the visitor or the container. It includes a suggestion of using an external iterator which makes sense to me if you have a regular tree and need to iterate differently.

Looking back to my oofRep visitor it had a series of levels of different classes to visit and had the iteration within methods like:

void
oofRepVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
    VisitBandList(inBands);
}


void
oofRepVisitor::VisitBandList(oofRepBandList& inBands)
{
    EnterLevel();
    const unsigned long numBands = inBands.count();
    for (unsigned long i=0; i<numBands; i++) {
        oofRepBand* theBand = inBands.value(i);
        assert(theBand);
        VisitTypedBand(theBand);
    }
    LeaveLevel();
}

with an override

void
OOF_repXMLlayoutVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
    oofRepStreamEnv::out() << mIdentities.getIndentString();
    if (inBands.keepTogether())
        oofRepStreamEnv::out()  << "<header>\n";    
    else  // default is ON, and simplifies XML
        oofRepStreamEnv::out()  << "<header keepTogether='false'>\n";
    VisitBandList(inBands);
    oofRepStreamEnv::out() 
        << mIdentities.getIndentString()
        << "</header>\n";
}
Andy Dent