1

I've been going over the Visitor Pattern to try to understand it. Is this a correct implementation of the Visitor Pattern for RPS??

diagram class

Here's the implementation code:

public enum ResultEnum {

    WIN,
    LOSE,
    DRAW

}

public interface ElementVisitor {

    ResultEnum visit(Rock rock);

    ResultEnum visit(Paper paper);

    ResultEnum visit(Scissors scissors);

}

public class PaperVisitor implements ElementVisitor {

    @Override
    public ResultEnum visit(Rock rock) {
        return ResultEnum.WIN;
    }

    @Override
    public ResultEnum visit(Paper paper) {
        return ResultEnum.DRAW;
    }

    @Override
    public ResultEnum visit(Scissors scissors) {
        return ResultEnum.LOSE;
    }

}

public class RockVisitor implements ElementVisitor {

    @Override
    public ResultEnum visit(Rock rock) {
        return ResultEnum.DRAW;
    }

    @Override
    public ResultEnum visit(Paper paper) {
        return ResultEnum.LOSE;
    }

    @Override
    public ResultEnum visit(Scissors scissors) {
        return ResultEnum.WIN;
    }

}

public class ScissorsVisitor implements ElementVisitor {

    @Override
    public ResultEnum visit(Rock rock) {
        return ResultEnum.LOSE;
    }

    @Override
    public ResultEnum visit(Paper paper) {
        return ResultEnum.WIN;
    }

    @Override
    public ResultEnum visit(Scissors scissors) {
        return ResultEnum.DRAW;
    }

}

public interface Element {

    ResultEnum accept(ElementVisitor elementVisitor);

}

public class Paper implements Element {

    @Override
    public ResultEnum accept(ElementVisitor elementVisitor) {
        return elementVisitor.visit(this);
    }

}

public class Rock implements Element {

    @Override
    public ResultEnum accept(ElementVisitor elementVisitor) {
        return elementVisitor.visit(this);
    }

}

public class Scissors implements Element {

    @Override
    public ResultEnum accept(ElementVisitor elementVisitor) {
        return elementVisitor.visit(this);
    }

}

@SpringBootApplication
public class TestsApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(TestsApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        Element e1 = new Rock();
        Element e2 = new Paper();

        System.out.println(e1.accept(new PaperVisitor()));
        System.out.println(e2.accept(new RockVisitor()));
    }

}

Result:
WIN
LOSE

I think I understang the gist of it (avoid the instanceof operator) but it seems a bit cumbersome in the sense that if you have N visitable elements, you need to implement N*N methods. In this case it's only 9 and you can combine every element with itself and other elements, but I can't think of a real case scenario where this would happen (in a IO bound web app). Have you ever implemented this pattern in a real web app??

4
  • 2
    Your example is weird and is giving you wrong image of the pattern. As it never happens that elemets and visitors are the same thing. Usually, elements are fixed with visitors being added. Study the expression problem.
    – Euphoric
    Commented Jun 12, 2021 at 18:01
  • I know it's not a real world example but I was asked this question in an interview and I had to solve it differently because I wasn't aware of this pattern. Have you used it in a real world app??
    – shulito
    Commented Jun 12, 2021 at 18:28
  • 1
    If I understood you well, the person expected you to use Visitor to solve this; if so, then I think whoever posed this question to you had a somewhat narrow understanding of the pattern. The pattern is often described as a way to do multiple-dispatch, i.e., determine which method to call based on the runtime types of more than one argument, and this kind of demonstrates that aspect. 1/2 Commented Jun 13, 2021 at 14:30
  • 1
    However, visitors are meant to be operations - each is an object that represents a function (in a general sense) that operates on a finite set of possible element types. I feel renaming a couple of things makes the pattern easier to understand. Read element.Accept(visitor) as element.Do(operation), and read visitor.visit(ConcreteElement) as operation.ApplyTo(ConcreteElement). Client code doesn't know the concrete type of an element, it just chooses an operation, and passes it to the element abstraction; the operation (visitor) then figures out which concrete method to apply. 2/2 Commented Jun 13, 2021 at 14:30

1 Answer 1

3

This is an interesting use of the implementation techniques behind the visitor pattern. However, is does not reflect the essence of the visitor pattern which is:

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.
-- E.Gamma & al. in Design Patterns: Elements of Reusable Object-Oriented Software

Your visitor only operates on one single element, so on a structure that is so simple that the visitor seems to be an overkill.

Moreover, your visitor does not let you easily define new operations without changing the structure on which it operates. For example, I could not use it, and instead of determining the winner , count the rocks, or evaluate a game strategy: the type of the result of the visitor is defined and constrained by the accept() interface in the structure, and not within the visitor. This indirect coupling narrows down the possible other use, and the original pattern avoids it.

However, congratulations: you have just reinvented a variant of the double-dispatch! And this is always a great and enriching personal experience ;-)

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.