Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visitor pattern's purpose with examples [duplicate]

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.

like image 356
Victor Avatar asked Apr 08 '10 23:04

Victor


People also ask

In which scenario would you use the visitor pattern?

The visitor pattern is used when: Similar operations have to be performed on objects of different types grouped in a structure (a collection or a more complex structure). There are many distinct and unrelated operations needed to be performed.

Should I use visitor pattern?

The visitor pattern is useful when you want to process a data structure containing different kinds of objects, and you want to perform a specific operation on each of them, depending on its type. Your example is not the best, since you pass a single homogeneous list as input, so there is really no need for the pattern.

How does the visitor design pattern work?

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures.

What is the purpose of the visitor pattern?

The purpose of a Visitor pattern is to define a new operation without introducing the modifications to an existing object structure. Imagine that we have a composite object which consists of components.


1 Answers

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.

like image 178
Juliet Avatar answered Oct 05 '22 03:10

Juliet