How does the Visitor Pattern not violate the Open/Closed Principle?
Asked Answered
R

3

30

From Wikipedia :

The idea was that once completed, the implementation of a class could only be modified to correct errors; new or changed features would require that a different class be created. That class could reuse coding from the original class through inheritance

From what I understand, The Visitor pattern is a powerful technique to traverse similar but different objects that implement the same Interface by the use of double dispatch. In one of my Java examples, I created a composite set of objects that form a tree structure, and each specific implementation of those objects implement the visitable interface. The visitor interface has a method for each of the visitable objects, and the concrete visitor implements what to do for each of those cases.

The thing I'm trying to get my head around is the fact that if I were to add a new implementation to the composite structure that also implements visitable, then I need to reopen the visitor interface and add that case to it, also forcing me to modify each implementation of visitor.

While this is fine, since I would need to do this anyway (What good is adding to your visitables if the visitor can't understand them?) but on an academic level, wouldn't this be violating the Open Closed Principle? Isn't that one of the core reasons for Design Patterns anyway? Trying to show a decent reason for switching to this pattern instead of maintaining a switch statement to end all switch statements, but everyone argues that the code will be the same anyways, with a method for each case instead of a switch block, just broken up and harder to read.

Rosetterosewall answered 20/12, 2012 at 19:29 Comment(0)
B
19

A pattern is applicable to certain cases. From the GoF book (p. 333):

Use the Visitor pattern when

  • [...]

  • the classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes.

If you frequently change the classes of the objects that make up the structure, the Visitor class hierarchy can be hard to maintain. In such a case, it may be easier to define operations on the classes that make up the structure.

Boyle answered 20/12, 2012 at 19:38 Comment(3)
So in the event that I have an object structure that changes often, but only really one visiting strategy, then it does not make sense to continue pursuing a visitor pattern. Do you know of any other elegant solutions to this? Should each object in the structure contain it's own logic that would normally be in the visitor?Rosetterosewall
Setting this as the accepted answer, specifically since it addresses the open close principle question, but an straight up honorable mention to dasblinkenlight's answer in this thread for a possible alternative implementation. Thanks guys!Rosetterosewall
I also would mention Acyclic VisitorInerrable
C
13

John Vlissides, one of the GoF, wrote an excellent chapter on the subject in his Patterns Hatching book. He discusses the very concern that extending the hierarchy is incompatible with maintaining the visitor intact. His solution is a hybrid between a visitor and an enum-based (or a type-based) approach, where a visitor is provided with a visitOther method called by all classes outside the "base" hierarchy that the visitor understands out of the box. This method provides you an escape way to treat the classes added to the hierarchy after the visitor has been finalized.

abstract class Visitable {
    void accept(Visitor v);
}
class VisitableSubclassA extends Visitable  {
    void accept(Visitor v) {
        v.visitA(this);
    }
}
class VisitableSubclassB extends Visitable {
    void accept(Visitor v) {
        v.visitB(this);
    }
}
interface Visitor {
    // The "boilerplate" visitor
    void visitB(VisitableSubclassA a);
    void visitB(VisitableSubclassB b);
    // The "escape clause" for all other types
    void visitOther(Visitable other);
}

When you add this modification, your visitor is no longer in violation of the Open-Close Principle, because it is open to extension without the need to modify its source code.

I tried this hybrid method on several projects, and it worked reasonably fine. My main class hierarchy is defined in a separately compiled library which does not need to change. When I add new implementations of Visitable, I modify my Visitor implementations to expect these new classes in their visitOther methods. Since both the visitors and the extending classes are located in the same library, this approach works very well.

P.S. There is another article called Visitor Revisited discussing precisely that question. The author concludes that one can go back to an enum-based double dispatch, because the original Visitor Pattern does not present a significant improvement over the enum-based dispatch. I disagree with the author, because in cases when the bulk of your inheritance hierarchy is solid, and the users are expected to provide a few implementations here and there, a hybrid approach provides significant benefits in readability; there is no point in throwing out everything because of a couple of classes that we can fit into the hierarchy with relative ease.

Convention answered 20/12, 2012 at 19:44 Comment(4)
I had another idea: Would it make sense at all to implement some sort of composition of visitors that handle different specific visitable types, and simply call the correct visitor in a chain of command style? I suppose at that point I'm just trying to fit a square peg in a round hole, though.Rosetterosewall
@Rosetterosewall I tried doing something like that (i.e. two levels of visitors) but it quickly got out of hand, so I dropped my experiment, and went back to the "visitor with an escape clause".Convention
Ever heard of the acyclic visitor? From c2.com: "The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern."Reheat
@Jordão Nice! I do not quite like the idea of having to cast when jumping through levels of visitors, but the idea looks very cool.Convention
I
3

The first two answers are great. To expand on the observation, "A pattern is applicable to certain cases," consider OCP in two dimensions.

  1. Object-oriented code is open for extension when we can add new types to an existing hierarchy. We cannot add new functions to the existing types.
  2. Functional code is open for extension when we can add new logic to existing data structures. We cannot pass new data structures through existing functions.

This dichotomy is called the expression problem. The Visitor Pattern allows us to trade the usual dimension in which OO is extensible, and in return we gain the dimension in which FP is extensible.

To reconcile Visitor with OCP, we might say the pattern simply opens a different dimension for extensibility. A tradeoff in the dimensions of extensibility is applicable to certain cases.

Indomitable answered 2/3, 2019 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.