Visitor Design Pattern explained in 2 minutes

Practical guide to explain Visitor Design Pattern

ยท

2 min read

Problem Statement

Consider the below Animal interface. If you want to add new methods to the interface, you have to update Cow and Dog classes to implement those methods.

public interface Animal {
}

public class Cow implements Animal {
}

public class Dog implements Animal {
}

But what if you want to add new functionalities across all the classes without worrying about changing them, then Visitor Design Pattern is perfect for you.

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

public class Cow implements Animal {
    @Override
    public <T> T accept(AnimalVisitor<T> visitor) {
        return visitor.visit(this);
    }
}

public class Dog implements Animal {
    @Override
    public <T> T accept(AnimalVisitor<T> visitor) {
        return visitor.visit(this);
    }
}

In the above scenario, let's say you want to add new functionality like Speak or NumberOfLegs, then you have to create an implementation of AnimalVisitor and implement your business logic directly in those classes.

public interface AnimalVisitor<T> {
    T visit(Cow cow);

    T visit(Dog dog);
}

public class LegsVisitor implements AnimalVisitor<Integer> {
    @Override
    public Integer visit(Cow cow) {
        return 4;
    }

    @Override
    public Integer visit(Dog dog) {
        return 4;
    }
}

public class SpeakVisitor implements AnimalVisitor<String> {
    @Override
    public String visit(Cow cow) {
        return "Moo";
    }

    @Override
    public String visit(Dog dog) {
        return "Bark";
    }
}

Driver Code

public class Main {
    public static void main(String[] args) {
        Animal cow = new Cow();
        Animal dog = new Dog();
        AnimalVisitor<Integer> legsVisitor = new LegsVisitor();
        AnimalVisitor<String> speakVisitor = new SpeakVisitor();

        System.out.println(cow.accept(legsVisitor));
        System.out.println(cow.accept(speakVisitor));
        System.out.println(dog.accept(legsVisitor));
        System.out.println(dog.accept(speakVisitor));
    }
}

Output:
4
Moo
4
Bark

What's the benefit?

  • If you refer to the code once more, then you will notice that we added new functionality to Cow and Dog class without changing it -- this functionality is critical when working with client libraries.

  • All the business logic is encapsulated in Visitor classes, which can help in segregating domain logic from model classes e.g. LegsVisitor contains all the logic for calculating the number of legs for all types of animals.

  • Adherence to Open-Closed Principle i.e. class is open to extension but closed for modifications.

  • Adherence to the Single Responsibility Principle i.e. class has only one responsibility.

What's the drawback?

  • If you have a lot of sub-classes, then the Visitor implementation must handle them even if you want to add new functionality to only one of the classes.

  • While adding a new class, you must update the existing visitor implementations to support the new class.

Did you find this article valuable?

Support Snehasish Roy by becoming a sponsor. Any amount is appreciated!

ย