Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using visitor pattern instead of casting

I make regular use of the visitor pattern in my code. When a class hierarchy has a visitor implemented, I use it as an alternative to instanceof and casting. However it leads to some pretty awkward code which I'd like to improve.

Consider the contrived case:

interface Animal {
    void accept(AnimalVisitor visitor);
}

class Dog implements Animal {
    void accept(AnimalVisitor visitor) {
        visitor.visit(this);
    }
}

class Cat implements Animal {
    void accept(AnimalVisitor visitor) {
        visitor.visit(this);
    }
}

interface AnimalVisitor {
    default void visit(Cat cat) {};
    default void visit(Dog dog) {};
}

In the majority of cases, to do something specific to dogs only (for example) I implement a visitor that implements the logic in its visit method - just as the pattern intends.

There are case, however, in which I want to return an optional dog from the visitor to use outside.

In these case I end up with some pretty ugly code:

List<Dog> dogs = new ArrayList<>();
animal.accept(new AnimalVisitor() {
    void visit(Dog dog) {
        dogs.add(dog);
    }
}
Optional<Dog> possibleDog = dogs.stream().findAny();

I can't assign possibleDog directly inside the visitor because it's not a final variable, hence the list.

This is pretty ugly and inefficient just to get around requirement for effective finality. I'd be interested in ideas of alternatives.

Alternatives I've considered:

Turning the visitor into a generic which can be given a return value

interface Animal {
    <T> T accept(AnimalVisitor<T> visitor);
}

interface AnimalVisitor <T> {
    default Optional<T> visit(Dog dog) { return Optional.empty(); }
    default Optional<T> visit(Cat cat) { return Optional.empty(); }
}

Creating an abstract visitor that contains most of the code and can be trivial extended to set the optional directly

abstract class AnimalCollector implements AnimalVisitor <T> {
    private Optional<T> result = Optional.empty;

    protected void setResult(T value) {
        assert !result.isPresent();
        result = Optional.of(value);
    }

    public Optional<T> asOptional() {
        return result;
    }
}

Use a stream builder instead of a list

Stream.Builder<Dog> dogs = Stream.builder();
animal.accept(new AnimalVisitor() {
    void visit(Dog dog) {
        dogs.accept(dog);
    }
}
Optional<Dog> possibleDog = dogs.build().findAny();

But I don't find these particularly elegant. They involve a lot of boilerplate just to implement basic asA logic. I tend to use the second solution in my code to keep the usage clean. Is there a simpler solution I'm missing?

Just to be clear, I'm not that interested in answers with some variant of "use instanceof and casts". I realise it would work in this trivial case but the situations I'm considering have quite complex use of visitors that include visiting composites and delegates which make casting impractical.

like image 228
sprinter Avatar asked May 25 '18 21:05

sprinter


People also ask

When would you use the visitor pattern?

Visitor design pattern is one of the behavioral design patterns. It is used when we have to perform an operation on a group of similar kind of Objects. With the help of visitor pattern, we can move the operational logic from the objects to another class.

What problems can the visitor design pattern solve?

The Visitor design pattern is one of the twenty-three well-known Gang of Four design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.

How does the visitor pattern work?

The Visitor pattern represents an operation to be performed on the elements of an object structure without changing the classes on which it operates. This pattern can be observed in the operation of a taxi company. When a person calls a taxi company (accepting a visitor), the company dispatches a cab to the customer.


1 Answers

By splitting the AnimalCollector idea in the question in two, I think we can create something quite succinct.

(These examples are based around the original AnimalVisitor interface in the question - with such as void visit(Cat cat); as the methods).

The 'collect to Optional' part works well extracted to a standalone class:

public class OptionalCollector<T> {
    private Optional<T> result = Optional.empty();

    public void setResult(T value) {
        result = Optional.of(value);
    }

    public Optional<T> asOptional() {
        return result;
    }
}

Another possibility though is a bit of 'visit by lambda' style coding. A few static factory methods enable a visitor to be easily defined without declaring methods in an anonymous inner class.

These are some example factory methods:

import java.util.function.Consumer;

public class AnimalVisitorFactory {
    static AnimalVisitor dogVisitor(Consumer<Dog> dogVisitor) {
        return new AnimalVisitor() {
            @Override
            public void visit(Dog dog) {
                dogVisitor.accept(dog);
            }
        };
    }

    static AnimalVisitor catVisitor(Consumer<Cat> catVisitor) {
        return new AnimalVisitor() {
            @Override
            public void visit(Cat cat) {
                catVisitor.accept(cat);
            }
        };
    }
}

These two parts can then be combined:

import static AnimalVisitorFactory.*;

OptionalCollector<Dog> collector = new OptionalCollector<>();
animal.accept(dogVisitor(dog -> collector.setResult(dog)));
Optional<Dog> possibleDog = collector.asOptional();

This is digressing beyond what's needed for the case in the question, but note the idea could be taken a bit further in a fluent API style. With similar dogVisitor() and catVisitor() default methods on the AnimalVisitor interface too, several lambdas could then be chained together to build a more complete visitor.

like image 107
df778899 Avatar answered Sep 18 '22 14:09

df778899