2

I have a class hierarchy of elements (more static) with different operations on them in another class hierarchy (more flexible). During execution there is exactly one element given, and based on it's type I want to perform a given operation. Instead of using some chained conditional like

if (elem instanceof ElementA) {
 operationObj.doForA(elem);
else if ...

I decided to use the Double Dispatch technique:

elem.do(operationObj) { operationObj.doForA(this); }

Looking at my case, it seems there are almost all components present that make up the Visitor Pattern:

  • A class hierarchy of elements
  • A class hierarchy of operations on these elements
  • A Double Dispatch functionality to pick the operation

The only thing missing is the traversing part (all elements are being visited), so I'm unsure if I should use the Visitor Pattern. However, the only difference would be the terminology: I just wouldn't call the operation classes "Visitors" and the Dispatch Method "accept"; everything else stays the same. This makes me wonder:

  • Is my approach an overkill because without the need for traversal, there is an easier solution?
  • Is my solution fine, but there just isn't a specific Design Pattern to it?
  • Is the traversal part optional and I should just go with the Visitor terminology, making it easier for other developers to understand my thinking?

Interested to hear your thoughts.

6
  • whats the reason not to just call operationObj.Do(elem)?
    – Ewan
    Commented Jan 18 at 11:08
  • why not shuffle your loop around and just implement the whole pattern?
    – Ewan
    Commented Jan 18 at 11:21
  • 2
    Visitors are often used in conjunction with the Composite pattern where some traversal may be required, but that is in no way essential.
    – amon
    Commented Jan 18 at 11:55
  • @Ewan by calling operationObj.Do(elem) I would have to do the Type Check in every operator - I want to avoid that. keep in mind that elem can be of different Types, and I cannot overload the do Method like that (at least not in Java). also, I don't know what you mean with the second comment. Commented Jan 18 at 12:04
  • @amon: the GoF Design Patterns states: "2. Who is responsible for traversing the object structure? A visitor must visit each element of the object structure. ..." This is mentioned under the "Implementation" section, so I thought its a general requirement. also, see this answer: softwareengineering.stackexchange.com/questions/429282/… Commented Jan 18 at 12:09

5 Answers 5

4

I have a class hierarchy of elements (more static) with different operations on them in another class hierarchy (more flexible). ... I decided to use the Double Dispatch technique:

elem.do(operationObj) { operationObj.doForA(this); }

Based on this, I'd say you've implemented the Visitor pattern exactly, and furthermore it's a well chosen design precisely because the operations hierarchy is supposed to be more flexible.

(Whether the pattern is immediately recognizable as such to other developers is a different question, and perhaps a less important one, as the Visitor pattern is one of the least understood patterns by the general developer community, so having names such as accept and visit is of limited utility. The elem.do(operation) scheme you've come up with probably communicates the intent better, and in fact I've used similar naming in this answer of mine (except that I renamed visit to applyTo). It provides a bit more in-depth treatment of what I'm about to say here, so check that out as well.

Besides being a double-dispatch mechanism, in it's core, the Visitor pattern allows you to treat a group of concrete types (your elements) as a single abstract type - so that your client code can be written in a way that avoids type checking. However, there's a catch. I said the design is well chosen because the Visitor pattern implies a certain tradeoff - it is hard to add new kinds of elements (new subtypes in the element type hierarchy), but it is easy to add new operations. The reason being that the dispatching mechanism is tightly coupled to the number of different element types, because in general you want to enforce that all operations work on all elements (even if sometimes it's a no-op, if a no-op makes sense in a given context) - because your client code should remain oblivious to the concrete type, and free to pass any operation to the do method of any element.

So, these sorts of designs are suitable when the number of different element types is expected to be relatively stable, while the number of different operations is to change more frequently (i.e., there's more flexibility on the operations side). From your description, it seems that this is exactly the case.

Note that this tradeoff is the opposite to that found in "normal" OOP dynamic polymorphism. There, you have an abstract type that defines a fixed interface (a limited set of abstract operations), that a number of derivatives has to implement in different ways (but within the constraints set by the abstraction) - so it's easy to add new derivatives (new types of elements), but it's hard to add new abstract operations (because a change to the interface propagates throughout the hierarchy).

BTW, this tradeoff is not something that's specific to the Visitor pattern, it's just that the pattern is a manifestation of one of the two basic approaches to data abstraction (again, see my other answer).

The only thing missing is the traversing part

It's true that in the Go4 book the Visitor pattern is introduced in the context of traversing an object structure, and that a common example is traversing something like an abstract syntax tree, or a scene graph. But it is not strictly necessary to have an obvious tree (or graph) structure to traverse. There may be no traversal at all, you might just want to have the sort of abstraction described above, precisely because you need the flexibility on the operations side, and because you may want the compiler to enforce that all element-specific variants of an operation are implemented.

That said, traversal might arise in a different way. Because you now have an abstract element type, and a mechanism to dispatch operations on it, you can create composite recursive types (where you have an element that contains one or more abstract elements (that may contain their own elements, and so on), so there's a tree internal to it, so when you apply the operation, you're traversing this internal tree. This sort of thing starts to resemble the algebraic data types you find in functional programming - were something like a List type might be represented in a way that's equivalent to an abstract List class at the root of a type hierarchy consisting of a concrete Empty type, and a concrete NonEmpty type that contains an item (list element) plus a "tail" that's an abstract List (kind of a linked-list structure, when you think about it). However, in object-oriented languages, this particular approach to design tends to be inefficient and clunky to work with (among other things, implementing operations properly on such recursive types might require a bit of a mindset shift, so there's a learning curve, and it gets even worse if you need type parameters/generics), but there might be a more constrained application that could work well for you.

Is my approach an overkill because without the need for traversal, there is an easier solution?

It might be an overkill, but that's something for you to decide, since among us here, you're the one who knows your problem domain the best. I don't think the need for traversal (or lack thereof) makes that much of a difference, though, you can do it with any of the options below.

The simplest alternative (that keeps the same extensibility properties/tradeoffs) is to just check the type within your operations (a bunch of ifs or a switch statement). With this approach, an operation may or may not be an object - it could just be a free function that accepts an instance of the abstract element type. On the one hand, it's conceptually simple and efficient enough, but on the other it is error prone and a bit awkward for the operation implementers. Same thing - relatively easy to add new operations, hard to add new element types. Again, you'd be writing client code in by calling operations on abstract elements, so the type checking is confined to the operation internals.

Depending on the language, you might be able to utilize pattern matching facilities, if they are available, as well as destructuring (these are features that some mainstream OO languages adopted relatively recently from the functional world). The hope here is that the type system is robust enough so that you at least get a compiler warning if you don't cover all the cases. It's not drastically different from the option above, but here, you're relying on these language features to do things in a more structured, systematic way.

With the Visitor pattern, at least in a statically typed language, you're sort of leveraging the help of the compiler by defining the set of abstract "visit" methods that must be overridden in derived operations (visitors).

There's also a somewhat unorthodox variant of the pattern where instead of having a visitor hierarchy, you make the do method (the accept method) take in several lambdas, one for each concrete element type, and then you only call the appropriate lambda - it's kind of like "poor man's pattern matching", and again, can get clunky, so YMMV with this.

As for the traversal itself (assuming it's not just a simple iteration over a list), you might place the traversal code in the elements, or in the operations - one thing Go4 points out is that in the latter case you'd be duplicating the traversal code for each concrete element type that's a composite, but that you might want to go down this route if the traversal process is somehow dependent on the details/results of the operations on the elements.

P.S. Finally, I'd also like to draw your attention to Erik Eidt's answer, since it mentions the so called "data oriented" programming that takes a very different approach, so might be worth considering before you decide to commit to this one.

1

Avoiding the question of what to call what you're doing, here's a possible alternative.

With data oriented programming, we have data structures that would collect and keep the elements of a certain type and/or property/quality.  In a simple example, a list or vector for each type or quality of element.  The point is to eliminate testing and handling of objects that are not relevant to some operation.  This eliminates traversal of object that are not relevant, plus the testing for relevance, since every element on a given vector is applicable by type or quality.  These eliminations can improve performance dramatically, which is why this approach is used in the game community.

Items can be moved from one vector to another when some quality changes.  For example, when an object is visible, it is on one vector, and when made invisible it moves to a different vector (and later back as needed).

Such design might replace your other data structures or be in addition to and augment your existing data structures.

0

What you have implemented is not recognisably the visitor pattern.

You could arguable about whether the pattern definition requires the iteration to be in a specific place, but it would be a very academic argument. If you want other programmers to glance at your code and go:

"ahh the visitor pattern! I remember being forced to learn this at uni, lets google how to do it again and add a new visitor for this feature i'm working on"

Then you should implement the full pattern with Visit() Accept() methods, XxxVisitor class names and some top level hierachy object that does the looping.

Whether you should go further and implement the full pattern, or less far and do things more simplistically really depends on the details of your code. How many different operations do you need to support? How changeable are the base objects over time? etc. Personally I think it's a rather niche pattern.

2
  • thanks for the input. it seems other commenters disagree with you. they say I basically implemented the Visitor pattern, so I decided to go with this terminology unless other objections are coming in. Commented Jan 18 at 14:07
  • I mean i don't want to call them out and out wrong, but perhaps you should look to see if there is any other example in the world of something being called "the visitor pattern" where calling accept doesnt traverse to child objects?
    – Ewan
    Commented Jan 18 at 14:58
0

The essence of the Visitor pattern is creating double-dispatch in a language that only supports single dispatch, so yes, your usage is a Visitor.

"Single Object" is an object structure. Such a structure doesn't really have a name, but it's equivalent to a list of length 1, or a tree of depth 1, etc.

4
  • the GoF Design Patterns states: "2. Who is responsible for traversing the object structure? A visitor must visit each element of the object structure. ..." This is mentioned under the "Implementation" section, so I thought its a general requirement. also, see this answer: softwareengineering.stackexchange.com/questions/429282/… Commented Jan 18 at 12:08
  • @PhilippMurry A single object is a kind of object structure
    – Caleth
    Commented Jan 18 at 12:27
  • but one you don't have to traverse. so I thought the traversing part is what makes the Visitor shine, and the Double Dispatching part is just a technique the Visitor uses, but not what it is. but thats too philosophical, so I guess I just go with the Visitor terminology. Commented Jan 18 at 12:46
  • 1
    @PhilippMurry it's an applicable pattern in languages that only have single dispatch. In languages with native multiple dispatch, you don't need to communicate "this is a Visitor" when you use the multiple dispatch mechanism
    – Caleth
    Commented Jan 18 at 13:05
0

Patterns are more about communication than just having a certain structure. While providing a double dispatch mechanism is sonething the visitor pattern does, you should consider what (family of) problems the pattern aims to solve: 1) add operations to a structure of elements without modifying the structure (expression Problem), or 2) executing different operations on each element of the structure

So in your case, you may have the structurally/syntactic components of the pattern, but dont really use it for the listed problem solution. Since calling it "visitor pattern" is about communicating the Problem/solution, it would be wrong to call it that

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.