Visitor pattern's purpose with examples [duplicate]
Asked Answered
H

6

96

I'm really confused about the visitor pattern and its uses. I can't really seem to visualize the benefits of using this pattern or its purpose. If someone could explain with examples if possible that would be great.

Haematin answered 8/4, 2010 at 23:41 Comment(3)
Did you check Wikipedia on the subject? en.wikipedia.org/wiki/Visitor_pattern What parts exactly are not clear? Or are you looking for real world examples?Archuleta
TL;DR: The visitor pattern is like higher-order map() with element-type specific behaviour called "visitor". Or say, the Visitor Pattern immitates dependable functions or dual dispatch. Dependable functions are basically a collection of function overloads but as a single function, with different input types (or even values) being mapped to different output types. (Their real use is in lanuages whose static code analysis can verify their type correctness for runtime-variable inputs.) In case of dual dispatch, the behaviour is dynamically dispatched by both a visited object and the visitor.Maroc
Btw, a reasonable use case that I saw most recently is code generation from intermediate representation trees. There is a Visitor class for each output language, defining how certain tree nodes (with different types) are translated to the target language. The core will just walk over the tree, using the visitor, to generate text output for each node.Maroc
Y
79

Once upon a time...

class MusicLibrary {
    private Set<Music> collection ...
    public Set<Music> getPopMusic() { ... }
    public Set<Music> getRockMusic() { ... }
    public Set<Music> getElectronicaMusic() { ... }
}

Then you realize you'd like to be able to filter the library's collection by other genres. You could keep adding new getter methods. Or you could use Visitors.

interface Visitor<T> {
    visit(Set<T> items);
}

interface MusicVisitor extends Visitor<Music>;

class MusicLibrary {
    private Set<Music> collection ...
    public void accept(MusicVisitor visitor) {
       visitor.visit( this.collection );
    }
}

class RockMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getRockMusic() { return this.picks; }
}
class AmbientMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getAmbientMusic() { return this.picks; }
}

You separate the data from the algorithm. You offload the algorithm to visitor implementations. You add functionality by creating more visitors, instead of constantly modifying (and bloating) the class that holds the data.

Yoohoo answered 9/4, 2010 at 0:11 Comment(7)
Sorry this is not really a good example for the Visitor pattern just too simplistic. The one of the main mechanisms of the visitor pattern, selection of functionality through the type (double dispatch) of the visited element is not shown -1Landpoor
After taking a Compilers course I've also come to realize how pointless this example is.Zig
@HaraldScheirich Visitors may or may not choose to select functionality by type. I've found visitors to be extremely useful even without that.Nursery
@Harald Scheirich: This example is too simplistic but you have to agree that it is a good example for beginners to understand visitor pattern.I think its a good enough example of visitor pattern.Wyman
@NoelAng Is this not a Strategy Design Pattern ?Buchalter
Seconding @HaraldScheirich - the formal representation of a Visitor (from "Design Patterns - Elements of Reusable Object-Oriented Software", 1995) shows that each Visitor should represent an operation, and that visitor should have a method for each type that should be operable.Outrigger
This example seems to add the same problem that Visitor aims to solve, by having differently named methods on the same MusicVisitor interface. A better solution would be to have a common getMusic() method on all of them.Hornwort
G
218

So you've probably read a bajillion different explanations of the visitor pattern, and you're probably still saying "but when would you use it!"

Traditionally, visitors are used to implement type-testing without sacrificing type-safety, so long as your types are well-defined up front and known in advance. Let's say we have a few classes as follows:

abstract class Fruit { }
class Orange : Fruit { }
class Apple : Fruit { }
class Banana : Fruit { }

And let's say we create a Fruit[]:

var fruits = new Fruit[]
    { new Orange(), new Apple(), new Banana(),
      new Banana(), new Banana(), new Orange() };

I want to partition the list in to three lists, each containing oranges, apples, or bananas. How would you do it? Well, the easy solution would be a type-test:

List<Orange> oranges = new List<Orange>();
List<Apple> apples = new List<Apple>();
List<Banana> bananas = new List<Banana>();
foreach (Fruit fruit in fruits)
{
    if (fruit is Orange)
        oranges.Add((Orange)fruit);
    else if (fruit is Apple)
        apples.Add((Apple)fruit);
    else if (fruit is Banana)
        bananas.Add((Banana)fruit);
}

It works, but there are lots of problems with this code:

  • For a start, its ugly.
  • Its not type-safe, we won't catch type errors until runtime.
  • Its not maintainable. If we add a new derived instance of Fruit, we need to do a global search for every place which performs a fruit type-test, otherwise we might miss types.

Visitor pattern solves the problem elegantly. Start by modifying our base Fruit class:

interface IFruitVisitor
{
    void Visit(Orange fruit);
    void Visit(Apple fruit);
    void Visit(Banana fruit);
}

abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); }
class Orange : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }
class Apple : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }
class Banana : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }

It looks like we're copy pasting code, but note the derived classes are all calling different overloads (the Apple calls Visit(Apple), the Banana calls Visit(Banana), and so on).

Implement the visitor:

class FruitPartitioner : IFruitVisitor
{
    public List<Orange> Oranges { get; private set; }
    public List<Apple> Apples { get; private set; }
    public List<Banana> Bananas { get; private set; }

    public FruitPartitioner()
    {
        Oranges = new List<Orange>();
        Apples = new List<Apple>();
        Bananas = new List<Banana>();
    }

    public void Visit(Orange fruit) { Oranges.Add(fruit); }
    public void Visit(Apple fruit) { Apples.Add(fruit); }
    public void Visit(Banana fruit) { Bananas.Add(fruit); }
}

Now you can partition your fruits without a type-test:

FruitPartitioner partitioner = new FruitPartitioner();
foreach (Fruit fruit in fruits)
{
    fruit.Accept(partitioner);
}
Console.WriteLine("Oranges.Count: {0}", partitioner.Oranges.Count);
Console.WriteLine("Apples.Count: {0}", partitioner.Apples.Count);
Console.WriteLine("Bananas.Count: {0}", partitioner.Bananas.Count);

This has the advantages of:

  • Being relatively clean, easy to read code.
  • Type-safety, type errors are caught at compile time.
  • Maintainability. If I add or remove a concrete Fruit class, I could modify my IFruitVisitor interface to handle the type accordingly, and the compiler will immediately find all places where we implement the interface so we can make the appropriate modifications.

With that said, visitors are usually overkill, and they have a tendency to grossly complicate APIs, and it can be very cumbersome to define a new visitor for every new kind of behavior.

Usually, simpler patterns like inheritance should be used in place of visitors. For example, in principle I could write a class like:

class FruitPricer : IFruitVisitor
{
    public double Price { get; private set; }
    public void Visit(Orange fruit) { Price = 0.69; }
    public void Visit(Apple fruit) { Price = 0.89; }
    public void Visit(Banana fruit) { Price = 1.11; }
}

It works, but what's the advantage over this trivial modification:

abstract class Fruit
{
    public abstract void Accept(IFruitVisitor visitor);
    public abstract double Price { get; }
}

So, you should use visitors when the following conditions hold:

  • You have a well-defined, known set of classes which will be visited.

  • Operations on said classes are not well-defined or known in advance. For example, if someone is consuming your API and you want to give consumers a way to add new ad-hoc functionality to objects. They're also a convenient way to extend sealed classes with ad-hoc functionaity.

  • You perform operations of a class of objects and want to avoid run-time type testing. This is usually the case when you traverse a hierarchy of disparate objects having different properties.

Don't use visitors when:

  • You support operations on a class of objects whose derived types are not known in advance.

  • Operations on objects are well-defined in advance, particularly if they can be inherited from a base class or defined in an interface.

  • Its easier for clients to add new functionality to classes using inheritance.

  • You are traversing a hierarchy of objects which have the same properties or interface.

  • You want a relatively simple API.

Grandpapa answered 9/4, 2010 at 2:41 Comment(4)
Except that the visitor is not closed for modification (Open/Closed principle). Any new fruits have to have a new method. It's better that the visitor only has a Visit(Fruit fruit) method and a concrete implementation which maps each fruit to a specific method. (So that other visitor classes can extend the concrete base)Assurance
@Assurance one of the formal consequences of the Visitor pattern is that whenever you add a new object that can be visited, a new method is created, so a violation of Open/Closed is implied in the consequences of this pattern. Having a Visit() method that does the type resolution offers tons of disadvantages, including not allowing IDEs, compilers, RTEs, etc to validate implementations.Outrigger
Could you refractor the code to java, please. Also, the question has a java tag so 'we' would prefer the example in java. I can't understand the code, it looks like pseudocode.Biblicist
Could you refractor the code to java, please. Also, the question has a java tag so 'we' would prefer the example in java. I can't understand the code, it looks like pseudocode.Biblicist
Y
79

Once upon a time...

class MusicLibrary {
    private Set<Music> collection ...
    public Set<Music> getPopMusic() { ... }
    public Set<Music> getRockMusic() { ... }
    public Set<Music> getElectronicaMusic() { ... }
}

Then you realize you'd like to be able to filter the library's collection by other genres. You could keep adding new getter methods. Or you could use Visitors.

interface Visitor<T> {
    visit(Set<T> items);
}

interface MusicVisitor extends Visitor<Music>;

class MusicLibrary {
    private Set<Music> collection ...
    public void accept(MusicVisitor visitor) {
       visitor.visit( this.collection );
    }
}

class RockMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getRockMusic() { return this.picks; }
}
class AmbientMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getAmbientMusic() { return this.picks; }
}

You separate the data from the algorithm. You offload the algorithm to visitor implementations. You add functionality by creating more visitors, instead of constantly modifying (and bloating) the class that holds the data.

Yoohoo answered 9/4, 2010 at 0:11 Comment(7)
Sorry this is not really a good example for the Visitor pattern just too simplistic. The one of the main mechanisms of the visitor pattern, selection of functionality through the type (double dispatch) of the visited element is not shown -1Landpoor
After taking a Compilers course I've also come to realize how pointless this example is.Zig
@HaraldScheirich Visitors may or may not choose to select functionality by type. I've found visitors to be extremely useful even without that.Nursery
@Harald Scheirich: This example is too simplistic but you have to agree that it is a good example for beginners to understand visitor pattern.I think its a good enough example of visitor pattern.Wyman
@NoelAng Is this not a Strategy Design Pattern ?Buchalter
Seconding @HaraldScheirich - the formal representation of a Visitor (from "Design Patterns - Elements of Reusable Object-Oriented Software", 1995) shows that each Visitor should represent an operation, and that visitor should have a method for each type that should be operable.Outrigger
This example seems to add the same problem that Visitor aims to solve, by having differently named methods on the same MusicVisitor interface. A better solution would be to have a common getMusic() method on all of them.Hornwort
I
5

It provides another layer of abstraction. Reduces complexity of an object and makes it more modular. Sorta like using an interface(implementation being completely independent and no one cares how it is done just that it gets done.)

Now I have never used it but it would be useful for: Implementing a particular function that needs to be done in different subclasses, since each of the sub classes needs to implement it in different ways another class would implement all the functions. Kinda like a module but only for a collection of classes. Wikipedia has a pretty good explanation: http://en.wikipedia.org/wiki/Visitor_pattern And their example helps explain what I am trying to say.

Hope that helps clear it up a bit.

EDIT**Sorry I linked to wikipedia for your answer but they really do have a decent example :) Not trying to be that guy that says go find it yourself.

Isochromatic answered 8/4, 2010 at 23:51 Comment(1)
This explanation sort of sounds like Strategy patternNebraska
S
4

Example of visitor pattern. Book, Fruit & Vegetable are basic elements of type "Visitable" and there are two "Visitors" , BillingVisitor & OfferVisitor each of the visitor has its own purpose .Algo to calculate the bill and algo to calculate the offers on these elements is encapsulated in the respective visitor and the Visitables ( Elements) remain the same.

import java.util.ArrayList;
import java.util.List;


public class VisitorPattern {

    public static void main(String[] args) {
        List<Visitable> visitableElements = new ArrayList<Visitable>();
        visitableElements.add(new Book("I123",10,2.0));
        visitableElements.add(new Fruit(5,7.0));
        visitableElements.add(new Vegetable(25,8.0));
        BillingVisitor billingVisitor = new BillingVisitor();
        for(Visitable visitableElement : visitableElements){
            visitableElement.accept(billingVisitor);
        }

        OfferVisitor offerVisitor = new OfferVisitor();
        for(Visitable visitableElement : visitableElements){
            visitableElement.accept(offerVisitor);
        }
        System.out.println("Total bill " + billingVisitor.totalPrice);
        System.out.println("Offer  " + offerVisitor.offer);

    }

    interface Visitor {
        void visit(Book book);
        void visit(Vegetable vegetable);
        void visit(Fruit fruit);
    }

    //Element
    interface Visitable{
        public void accept(Visitor visitor);
    }


    static class OfferVisitor implements Visitor{
        StringBuilder offer = new StringBuilder();

        @Override
        public void visit(Book book) {
            offer.append("Book " +  book.isbn +  " discount 10 %" + " \n");
        }

        @Override
        public void visit(Vegetable vegetable) {
            offer.append("Vegetable  No discount \n");
        }

        @Override
        public void visit(Fruit fruit) {
            offer.append("Fruits  No discount \n");
        }

    }

    static class BillingVisitor implements Visitor{
        double totalPrice = 0.0;

        @Override
        public void visit(Book book) {
            totalPrice += (book.quantity * book.price);
        }

        @Override
        public void visit(Vegetable vegetable) {
            totalPrice += (vegetable.weight * vegetable.price);
        }

        @Override
        public void visit(Fruit fruit) {
            totalPrice += (fruit.quantity * fruit.price);
        }

    }

    static class Book implements Visitable{
        private String isbn;
        private double quantity;
        private double price;

        public Book(String isbn, double quantity, double price) {
            this.isbn = isbn;
            this.quantity = quantity;
            this.price = price;
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
    }

    static class Fruit implements Visitable{
        private double quantity;
        private double price;

        public Fruit(double quantity, double price) {
            this.quantity = quantity;
            this.price = price;
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
    }

    static class Vegetable implements Visitable{
        private double weight;
        private double price;

        public Vegetable(double weight, double price) {
            this.weight = weight;
            this.price = price;
        }


        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);            
        }
    }


}
Sierra answered 23/11, 2014 at 13:10 Comment(1)
I'm thinking I might be able to use the visitor concept in a game project to solve the problem of having flexible stat modifiers. It gets a little complicated though ... "modifiers" will have a bit of data like a name, desc, value and a modType (whether it's additive, multiplicative, etc and if it stacks with other modifiers or not) and it may have min/max output ranges. The stat object type is like class Stat<T> where T: struct and the relationships are a little daunting lolAnorthic
L
4

I think the main purpose of visitor pattern is it has high extensibility. The intuition is you've bought a robot. The robot already has fully implemented elementary functionalities as go ahead, turn left, turn right, go back, pick something, speak a phase, …

One day, you want your robot can go to post office for you. With all of these elementary functionalities, it can do, but you need to bring you robot to the shop and "update" your robot. The shop seller do not need to modify the robot, but simply put a new update chip to your robot and it can do what you want.

An other day, you want your robot to go to supermarket. Same process, you has to bring your robot to the shop and update this "advanced" functionality. No need to modify the robot itself.

and so on …

So the idea of Visitor pattern is, given all implemented elementary functionalities, you can use visitor pattern to add an infinite number of sophisticated functionalities. In the example, the robot is your worker classes, and the "update chip" are visitors. Each time need a new "update" of functionality, you don't modify your worker class, but you add a visitor.

Lundin answered 3/3, 2015 at 17:20 Comment(0)
S
2

It is to separate the data manipulation from the actual data. As a bonus you can reuse the same visitor class for the whole hierarchy of your classes, which again saves you from carrying around the data manipulation algorithms that are irrelevant to your actual objects.

Superbomb answered 8/4, 2010 at 23:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.