41

So, I was just reading about the Visitor pattern and I found the back and forth between the Visitor and the Elements very strange!

Basically we call the element, we pass it a visitor and then the element passes itself to the visitor. AND THEN the visitor operates the element. What? Why? It feels so unnecessary. I call it the "back and forth madness".

So, the intention of the Visitor is to decouple the Elements from their actions when the same actions need to be implemented across all the elements. This is done in case we need to extend our Elements with new actions, we don't want to go into all those classes and modify code that is already stable. So we're following the Open/Closed principle here.

Why is there all this back-and-forth and what do we lose if we don't have this?

For example, I made this code that keeps that purpose in mind but skips the interaction madness of the visitor pattern. Basically I have Animals that jump and eat. I wanted to decouple those actions from the objects, so I move the actions to Visitors. Eating and jumping increases the animal health (I know, this is a very silly example...)

public interface AnimalAction { // Abstract Visitor
    public void visit(Dog dog);
    public void visit(Cat cat);
}

public class EatVisitor implements AnimalAction { // ConcreteVisitor
    @Override
    public void visit(Dog dog) {
        // Eating increases the dog health by 100
        dog.increaseHealth(100);
    }

    @Override
    public void visit(Cat cat) {
        // Eating increases the cat health by 50
        cat.increaseHealth(50);
    }
}

public class JumpVisitor implements AnimalAction { // ConcreteVisitor
    public void visit(Dog dog) {
        // Jumping increases the dog health by 10
        dog.increaseHealth(10);
    }

    public void visit(Cat cat) {
        // Jumping increases the cat health by 20
        cat.increaseHealth(20);
    }
}

public class Cat { // ConcreteElement
    private int health;

    public Cat() {
        this.health = 50;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

public class Dog { // ConcreteElement

    private int health;

    public Dog() {
        this.health = 10;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();

        Dog dog = new Dog();
        Cat cat = new Cat();

        jumpAction.visit(dog); // NOTE HERE. NOT DOING THE BACK AND FORTH MADNESS.
        eatAction.visit(dog);
        System.out.println(dog.getHealth());

        jumpAction.visit(cat);
        eatAction.visit(cat);
        System.out.println(cat.getHealth());
    }
}

SDJ
  • 4,083
  • 1
  • 17
  • 35
AFP_555
  • 2,392
  • 4
  • 25
  • 45
  • 2
    IMHO when reasoning with visitor I usually use a tree pattern where you have `Node`s of different types (e.g. think of an AST where some nodes represent operators, and other operands etc so you represent `1+1=2` as `Equals(Plus(Literal(1), Literal(1)), Literal(2))` for example Obviously `Equals`/`Plus` etc are generic since the expressions need not be simple literals and now think of how you'd go to write a visitor that evalutes this expression... Then write some code that parses the text of an expression and evaluates it, now you have 0 concrete classes known at compile time – GACy20 Jun 04 '21 at 07:31
  • 1
    @GACy20 You're right. The only reason why this code is working is because I already know the dog is a Dog and the cat is a Cat. Thanks for this example, it gives much more sense to the visitor. – AFP_555 Jun 04 '21 at 07:36
  • 3
    The code working with `dog` and the code working with `cat` *look* identical, so **what happens if you try to put them into a list and use a loop**? – Ben Voigt Jun 04 '21 at 22:15
  • I think that Shvets rationalises the visitor pattern well; you use it when you need to ***extend existing classes*** to implement some common behaviour - due to this you need to use "double despatch" - https://sourcemaking.com/design_patterns/visitor When you don't have that constraint to deal with there are usually simpler methods of achieving the same goal. – Den-Jason Jun 05 '21 at 18:41
  • 1
    See also my answer [here](https://softwareengineering.stackexchange.com/a/412392/275536), it might bring you some further insight. – Filip Milovanović Jun 05 '21 at 18:52
  • I personally don’t get the benefit of this pattern either. I don’t see any benefit over a single method that checks for the concrete types and delegates accordingly. You need to know all supported concrete classes (that need to implement a meaningless interface) in advance, making the pattern 0 flexible. And the indirection of the accept method makes it very unintuitive. Plus it’s bad practice to add behavioral code to data classes - IMHO even if it’s generic code. The only benefit you get is, that by getting all implementation of the visitor you get these kind of usages of your classes. – dpr Jun 05 '21 at 22:59
  • I've been reading this pattern for a while, and I find it horrible, I am quite angry, this pattern `hides` everything, and on top of that it uses the word `visit`, that doesn't tell the programmer anything, only that something is going to happen, not what, also if I look at the JAVA example on `Wikipedia`, I see the class `CarElementPrintVisitor` that really can be made to create that same class without having to add the methods in depth of visit, that is to say `CarElementPrint->printBody(Body)`, I believe that this speaks more by itself that the `visitor pattern`. Or have I missed something? – jcarlosweb Nov 29 '22 at 23:32

5 Answers5

38

The code in the OP resembles a well-known variation of the Visitor design pattern known as an Internal Visitor (see e.g. Extensibility for the Masses. Practical Extensibility with Object Algebras by Bruno C. d. S. Oliveira and William R. Cook). That variation, however, uses generics and return values (instead of void) to solve some of the problems that the Visitor pattern addresses.

Which problem is that, and why is the OP variation probably insufficient?

The main problem addressed by the Visitor pattern is when you have heterogenous objects that you need to treat the same. As the Gang of Four, (the authors of Design Patterns) states, you use the pattern when

"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."

What's missing from this sentence is that while you'd like to "perform operations on these objects that depend on their concrete classes", you want to treat those concrete classes as though they have a single polymorphic type.

A period example

Using the animal domain is rarely illustrative (I'll get back to that later), so here's another more realistic example. Examples are in C# - I hope they're still useful to you.

Imagine that you're developing an online restaurant reservation system. As part of that system, you need to be able to show a calendar to users. This calendar could display how many remaining seats are available on a given day, or list all reservations on the day.

Sometimes, you want to display a single day, but at other times, you want to display an entire month as a single calendar object. Throw in an entire year for good measure. This means that you have three periods: year, month, and day. Each has differing interfaces:

public Year(int year)

public Month(int year, int month)

public Day(int year, int month, int day)

For brevity, these are just the constructors of three separate classes. Many people might just model this as a single class with nullable fields, but this then forces you to deal with null fields, or enums, or other kinds of nastiness.

The above three classes have different structure because they contain different data, yet you'd like to treat them as a single concept - a period.

To do so, define an IPeriod interface:

internal interface IPeriod
{
    T Accept<T>(IPeriodVisitor<T> visitor);
}

and make each class implement the interface. Here's Month:

internal sealed class Month : IPeriod
{
    private readonly int year;
    private readonly int month;

    public Month(int year, int month)
    {
        this.year = year;
        this.month = month;
    }

    public T Accept<T>(IPeriodVisitor<T> visitor)
    {
        return visitor.VisitMonth(year, month);
    }
}

This enables you to treat the three heterogenous classes as a single type, and define operations on that single type without having to change the interface.

Here, for example, is an implementation that calculates the previous period:

private class PreviousPeriodVisitor : IPeriodVisitor<IPeriod>
{
    public IPeriod VisitYear(int year)
    {
        var date = new DateTime(year, 1, 1);
        var previous = date.AddYears(-1);
        return Period.Year(previous.Year);
    }

    public IPeriod VisitMonth(int year, int month)
    {
        var date = new DateTime(year, month, 1);
        var previous = date.AddMonths(-1);
        return Period.Month(previous.Year, previous.Month);
    }

    public IPeriod VisitDay(int year, int month, int day)
    {
        var date = new DateTime(year, month, day);
        var previous = date.AddDays(-1);
        return Period.Day(previous.Year, previous.Month, previous.Day);
    }
}

If you have a Day, you'll get the previous Day, but if you have a Month, you'll get the previous Month, and so on.

You can see the PreviousPeriodVisitor class and other Visitors in use in this article, but here are the few lines of code where they're used:

var previous = period.Accept(new PreviousPeriodVisitor());
var next = period.Accept(new NextPeriodVisitor());

dto.Links = new[]
{
    url.LinkToPeriod(previous, "previous"),
    url.LinkToPeriod(next, "next")
};

Here, period is an IPeriod object, but the code doesn't know whether it's a Day, and Month, or a Year.

To be clear, the above example uses the Internal Visitor variation, which is isomorphic to a Church encoding.

Animals

Using animals to understand object-oriented programming is rarely illuminating. I think that schools should stop using that example, as it's more likely to confuse than help.

The OP code example doesn't suffer from the problem that the Visitor pattern solves, so in that context, it's not surprising if you fail to see the benefit.

The Cat and Dog classes are not heterogenous. They have the same class field and the same behaviour. The only difference is in the constructor. You could trivially refactor those two classes to a single Animal class:

public class Animal {
    private int health;

    public Animal(int health) {
        this.health = health;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}

Then define two creation methods for cats and dogs, using the two distinct health values.

Since you now have a single class, no Visitor is warranted.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • I have a question. This whole back and forth is necessary because Java doesn't have double-dispatch. IF Java had double dispatch, could I use the same code I have (no back and forth) and treat the Dog and Cat from my example as Animal instead of their concrete classes? – AFP_555 Jun 04 '21 at 07:31
  • 1
    @AFP_555 That's speculative: *If your language had a language feature it doesn't have, one that solves a problem addressed by a design pattern, would you then need the pattern?* Tautologically: no. This is a typical (and entirely reasonable) criticism raised (particularly by the FP community) against DP: that it solves problems that 'better' languages don't have. – Mark Seemann Jun 04 '21 at 07:51
  • @MarkSeemann you're talking here about heterogeneous objects and it really got me thinking about a conceptual issue I'm having with the Strategy Pattern. I'm not sure if requesting this is "illegal", but could you check that question too? https://stackoverflow.com/questions/67833661/strategy-pattern-on-heterogeneous-objects – AFP_555 Jun 04 '21 at 08:08
  • @AFP_555 That is not at all illegal I think, however, that you may be misunderstanding my use of the term *heterogenous*. This may very well be my fault. What I mean by heterogeneity is types of objects that share no discernible similarities. Your list examples in that question are not like that. At the very least I think they may all be iterable (but, surprise, surprise! I'm not sure, as I'm actually not a Java programmer). – Mark Seemann Jun 04 '21 at 10:39
23

The back-and-forth in the Visitor is to emulate a kind of double dispatch mechanism, where you select a method implementation based on the run-time type of two objects.

This is useful if the type of both your animal and visitor are abstract (or polymorphic). In which case you have a potential of 2 x 2 = 4 method implementations to choose from, based on a) what kind of action (visit) you want to do, and b) what type of animal you want this action to apply to.

enter image description here enter image description here

If you are using concrete and non-polymorphic types, then part of this back-and-forth is indeed superfluous.

SDJ
  • 4,083
  • 1
  • 17
  • 35
  • 10
    This. Double dispatch is the key concept here, and its importance to the Visitor pattern would be emphasized by changing the conventional method name "`accept`" to something more like `revealYourClassToThisGuy`. – John Bollinger Jun 03 '21 at 21:46
  • @JohnBollinger Your comment made me understand the point. I tried using an Animal interface instead of concrete animals (Dog, Cat) when creating the animal objects, and of course, the code wouldn't compile. So... The point of the accept method is for the concrete animal to say to the visitor "Hey, I'm a Cat because I'm calling visitCat method on you. Now you can treat me like a cat.". I still don't understand the concept of double-dispatch though – AFP_555 Jun 04 '21 at 07:11
  • 2
    @AFP_555, it might help to have a look at [What is Method Dispatch?](https://stackoverflow.com/a/1811769/2402272) In Java, this is an activity with both compile-time and run-time parts. Double dispatch just means that you chain two method dispatches to achieve what you're after instead of just one. It is exactly the "back and forth madness" you criticize in the question, and this is the mechanism that makes the Visitor pattern work. – John Bollinger Jun 04 '21 at 14:24
8

With BACK-AND-FORTH, do you mean this?

public class Dog implements Animal {

    //...

    @Override
    public void accept(AnimalAction action) {
        action.visit(this);
    }
}

The purpose of this code is that you can dispatch on the type without knowing the concrete type, like here:

public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();


        Animal animal = aFunctionThatCouldReturnAnyAnimal();
        animal.accept(jumpAction);
        animal.accept(eatAction);
    }

    private static Animal aFunctionThatCouldReturnAnyAnimal() {
        return new Dog();
    }
}

So what you get is: You can call the correct individual action on an animal with only knowing that it is an animal.

This is especially useful if you traverse a composite pattern, where the leaf nodes are Animals and the inner nodes are aggregations (e.g. a List) of Animals. A List<Animal> cannot be processed with your design.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
CoronA
  • 7,717
  • 2
  • 26
  • 53
  • 4
    The principle shown here that is missing from the OP is, *Program to an interface, not an implementation.* The OP is programming directly to `Dog` and `Cat` so there is no polymorphism, and thus OO design patterns are not very useful. – jaco0646 Jun 03 '21 at 11:09
  • 2
    Yeha, that's right, I can't treat Dog dog as Animal dog, the code won't compile that way. This is a very peculiar Java thing. Now I understand that the back and forth is completely necessary, even if it is extremely awkward. Thanks. – AFP_555 Jun 04 '21 at 07:05
  • Yes to List. Just glancing at the OP's question it was obvious they'd never seen the standard "for(Animal a : myAnimals) { a.doThing(); }" example over a list of Cats, Dogs and a previously unknown class Ferret to understand dynamic dispatch. – Owen Reynolds Jun 04 '21 at 16:41
  • 1
    @jaco0646 Wait... I'm just reading the GoF book and they say this: "An iterator can't work across object structures with different types of elements. (...) Visitor does not have this restriction. It can visit objects that DON'T HAVE A COMMON PARENT CLASS. You can add any type of object to a Visitor interface. MyType and YourType do not have to be related through inheritance at all"...... So.... We don't really need the Animal parent class, that's the point of the visitor then, being able to traverse unrelated objects that don't belong to a same hierarchy. So why should I have the Animal class? – AFP_555 Jun 05 '21 at 11:10
  • Parent class is not needed, but the interface must provide an `accept(Visitor visitor)`-method, and each of the traversed objects must implement it. – CoronA Jun 05 '21 at 11:50
  • @AFP_555, in the phrase, "_visit objects that don't have a common parent class_," the key word is _common_ (not parent). Rather than relating all elements through `Animal`, another example could be `Canine dog` and `Feline cat` with no relation between the two parent classes. Within OOP, you always program to an interface. Visitor says you don't always have to program to _the same_ interface. If you're not programming to any interface whatsoever, then firstly you're not doing OOP, and secondly, the Visitor pattern is pointless, as shown in the OP. – jaco0646 Jun 05 '21 at 14:07
  • @CoronA Yeha, you're right. They do need a common interface with the accept method at least. So the objects can actually be totally different things and just have this "Visitable" interface and they could be traversed by a visitor. Nice. I think I get it now, the Visitor gives us a way of traversing completely unrelated objects by giving them a "Visitable" interface. – AFP_555 Jun 06 '21 at 06:44
3

The visitor pattern solves the problem of applying a function to the elements of a graph structure.

More specifically, it solves the problem of visiting every node N in some graph structure, in the context of some object V, and for every N, invoking some generic function F(V, N). The method implementation of F is chosen based on the type of V and of N.

In programming languages that have multiple dispatch, the visitor pattern almost disappears. It reduces to a walk of the graph object (e.g. recursive tree descent), which makes a simple F(V, N) call for every N node. Done!

For instance in Common Lisp. For brevity, let's not even define classes: integers and strings are classes, so let's use those.

First, let's write the four methods of the generic function, for every combination of an integer or string visiting an integer or string. The methods just produce output. We don't define the generic function with defgeneric; Lisp infers this and does it implicitly for us:

(defmethod visit ((visitor integer) (node string))
  (format t "integer ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor integer) (node integer))
  (format t "integer ~s visits integer ~s!~%" visitor node))

(defmethod visit ((visitor string) (node string))
  (format t "string ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor string) (node integer))
  (format t "string ~s visits integer ~s!~%" visitor node))

Now let's use a list as our structure to be iterated by the visitor, and write a wrapper function for that:

(defun visitor-pattern (visitor list)
  ;; map over the list, doing the visitation
  (mapc (lambda (item) (visit visitor item)) list)
  ;; return  nothing
  (values))

Test interactively:

(visitor-pattern 42 '(1 "abc"))
integer 42 visits integer 1!
integer 42 visits string "abc"!

(visitor-pattern "foo" '(1 "abc"))
string "foo" visits integer 1!
string "foo" visits string "abc"!

OK, so that's the Visitor Pattern: a traversal of every element in a structure, with a double dispatch of a method with a visiting context object.

The "back and forth madness" has to do with the boiler-plate code of simulating double dispatch in an OOP system which has only single dispatch, and in which methods belong to classes rather than being specializations of generic functions.

Because in mainstream single-dispatch OOP system, methods are encapsulated in classes, the first problem we have is where does the visit method live? Is it on the visitor or the node?

The answer turns out that it has to be both. We will need to dispatch something on both types.

Next comes the problem that in OOP practice, we need good naming. We cannot have a visit method on both the visitor and the visited object. When a visited object is visited, the "visit" verb is not used to describe what that object is doing. It "accepts" a visitor. So we have to call that half of the action accept.

We create a structure whereby each node to be visited has an accept method. This method is dispatched on the type of the node, and takes a Visitor argument. In fact, the node has multiple accept methods, which are statically specialized on different kinds of visitors: IntegerVisitor, StringVisitor, FooVisitor. Note that we can't just use String, even if we have such a class in the language, because it doesn't implement the Visitor interface with the visit method.

So what happens is we walk the structure, get every node N, and then call V.visit(N) to get the visitor to visit it. We don't know the exact type of V; it's a base reference. Each Visitor implementation must implement visit as piece of boiler plate (using a pseudo-language that isn't Java or C++):

StringVisitor::visit(Visited obj)
{
  obj.Accept(self)
}

IntegerVisitor::visit(Visited obj)
{
  obj.Accept(self)
}

The reason is that self has to be statically typed for the Accept call, because the Visited object has multiple Accept implementations for different types chosen at compile time:

IntegerNode::visit(StringVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}

IntegerNode::visit(IntegerVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}

All those classes and methods have to be declared somewhere:

class VisitorBase {
  virtual void Visit(VisitedBase);
}

class IntegerVisitor;
class StringVisitor;

class VisitedBase {
  virtual void Accept(IntegerVisitor);
  virtual void Accept(StringVisitor);
}

class IntegerVisitor : inherit VisitorBase {
  Integer value;
  void Visit(VisitedBase);
}

class StringVisitor: inherit VisitorBase {
  String value;
  void Visit(VisitedBase);
}

class IntegerNode : inherit VisitedBase {
  Integer value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}

class StringNode : inherit VisitedBase {
  String value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}

So that's the single-dispatch-with-static-overloading visitor pattern: there is a bunch of boiler plate, plus the limitation that one of the classes, either visitor or visited, has to know the static types of all of the others that are supported, so it can dispatch statically on it, and for each static type, there will be a dummy method as well.

Kaz
  • 55,781
  • 9
  • 100
  • 149
0
  • My Understanding

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

  • Generic example

Visitor root intrface

public interface Visitor<R> {

    R visit(Object o);

    static <T, R> X<T, R> forType(Class<T> type) {
        return () -> type;
    }

    static <R> Visitor<R> of(VisitorConsumer<VisitorBuilder<R>> visitorConsumer) {
        Map<Class<?>, Function<Object, R>> registry = new HashMap<>();
        VisitorBuilder<R> visitorBuilder = new VisitorBuilder<R>() {
            @Override
            public <T> void register(Class<T> type, Function<T, R> function) {
                registry.put(type, function.compose(type::cast));
            }
        };
        visitorConsumer.accept(visitorBuilder);
        return o -> (R) registry.get(o.getClass()).apply(o);
    }

    interface X<T, R> {
        default Y<R> execute(Function<T, R> function) {
            return visitorBuilder -> visitorBuilder.register(type(), function);
        }

        Class<T> type();
    }

    interface Y<R> extends VisitorConsumer<VisitorBuilder<R>> {
        default <T> W<T, R> forType(Class<T> type) {
            return index -> index == 0 ? this : type;
        }

        default Y<R> andThen(Y<R> after) {
            return t -> {
                this.accept(t);
                after.accept(t);
            };
        }
    }

    interface W<T, R> {

        Object get(int index);

        default Class<T> type() {
            return (Class<T>) get(1);
        }

        default Y<R> previousConsumer() {
            return (Y<R>) get(0);
        }

        default Y<R> execute(Function<T, R> function) {
            return previousConsumer().andThen(visitorBuilder -> visitorBuilder.register(type(), function));
        }
    }
}

Visitor consumerinterface

public interface VisitorConsumer<T> {

    void accept(T t);

    default VisitorConsumer<T> chainConsumer(VisitorConsumer<T> other) {
        Objects.requireNonNull(other);
        return (T t) -> {
            this.accept(t);
            other.accept(t);
        };
    }

    default <U> Z<U> construct(Class<?> type) {
        return () -> type;
    }

    interface Z<U> {
        default U build(VisitorConsumer<U> consumer, Supplier<U> supplier) {

            BiFunction<VisitorConsumer<U>, Supplier<U>, U> extractor = (o, s) -> {
                U u = s.get();
                o.accept(u);
                return u;
            };
            return extractor.apply(consumer, supplier);
        }

        Class<?> type();
    }
    
}

Visitor builder interface

public interface VisitorBuilder<R> {

    <T> void register(Class<T> type, Function<T, R> function);

}

And an implementation

public class Main {
    public static void main(String[] args) {
        VisitorConsumer<VisitorBuilder<String>> consumer = Visitor.<Beta, String>forType(Beta.class)
                .execute(o -> "The Great : " + o.getName())
                .forType(Alpha.class).execute(Alpha::getName);

        Visitor<String> visitor = Visitor.of(consumer);
        System.out.println(visitor.visit(new Beta("Wolf")));
        System.out.println(visitor.visit(new Alpha("Wolf")));
    }
}

In which Alpha and Beta are just to arbitrary different class with constructor that receive a string. Note that how we specifically added operators for each specific class type and applied visitor upon their instances.

Lunatic
  • 1,519
  • 8
  • 24