When should I use the Visitor Design Pattern?

2019-01-01 02:53发布

I keep seeing references to the visitor pattern in blogs but I've got to admit, I just don't get it. I read the wikipedia article for the pattern and I understand its mechanics but I'm still confused as to when I'd use it.

As someone who just recently really got the decorator pattern and is now seeing uses for it absolutely everywhere I'd like to be able to really understand intuitively this seemingly handy pattern as well.

21条回答
骚的不知所云
2楼-- · 2019-01-01 03:22

I'm not very familiar with the Visitor pattern. Let's see if I got it right. Suppose you have a hierarchy of animals

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Suppose it is a complex hierarchy with a well-established interface.)

Now we want to add a new operation to the hierarchy, namely we want each animal to make its sound. As far as the hierarchy is this simple, you can do it with straight polymorphism:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

But proceeding in this way, each time you want to add an operation you must modify the interface to every single class of the hierarchy. Now, suppose instead that you are satisfied with the original interface, and that you want to make the fewest possible modifications to it.

The Visitor pattern allows you to move each new operation in a suitable class, and you need to extend the hierarchy's interface only once. Let's do it. First, we define an abstract operation (the "Visitor" class in GoF) which has a method for every class in the hierarchy:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Then, we modify the hierarchy in order to accept new operations:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Finally, we implement the actual operation, without modifying neither Cat nor Dog:

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Now you have a way to add operations without modifying the hierarchy anymore. Here is how it works:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}
查看更多
高级女魔头
3楼-- · 2019-01-01 03:25

Visitor Pattern as the same underground implementation to Aspect Object programming..

For example if you define a new operation without changing the classes of the elements on which it operates

查看更多
裙下三千臣
4楼-- · 2019-01-01 03:26

In my opinion, the amount of work to add a new operation is more or less the same using Visitor Pattern or direct modification of each element structure. Also, if I were to add new element class, say Cow, the Operation interface will be affected and this propagates to all existing class of elements, therefore requiring recompilation of all element classes. So what is the point?

查看更多
无与为乐者.
5楼-- · 2019-01-01 03:26

Visitor

Visitor allows one to add new virtual functions to a family of classes without modifying the classes themselves; instead, one creates a visitor class that implements all of the appropriate specializations of the virtual function

Visitor structure:

enter image description here

Use Visitor pattern if:

  1. Similar operations have to be performed on objects of different types grouped in a structure
  2. You need to execute many distinct and unrelated operations. It separates Operation from objects Structure
  3. New operations have to be added without change in object structure
  4. Gather related operations into a single class rather than force you to change or derive classes
  5. Add functions to class libraries for which you either do not have the source or cannot change the source

Even though Visitor pattern provides flexibility to add new operation without changing the existing code in Object, this flexibility has come with a drawback.

If a new Visitable object has been added, it requires code changes in Visitor & ConcreteVisitor classes. There is a workaround to address this issue : Use reflection, which will have impact on performance.

Code snippet:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Explanation:

  1. Visitable (Element) is an interface and this interface method has to be added to a set of classes.
  2. Visitor is an interface, which contains methods to perform an operation on Visitable elements.
  3. GameVisitor is a class, which implements Visitor interface ( ConcreteVisitor).
  4. Each Visitable element accept Visitor and invoke a relevant method of Visitor interface.
  5. You can treat Game as Element and concrete games like Chess,Checkers and Ludo as ConcreteElements.

In above example, Chess, Checkers and Ludo are three different games ( and Visitable classes). On one fine day, I have encountered with a scenario to log statistics of each game. So without modifying individual class to implement statistics functionality, you can centralise that responsibility in GameVisitor class, which does the trick for you without modifying the structure of each game.

output:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Refer to

oodesign article

sourcemaking article

for more details

Decorator

pattern allows behaviour to be added to an individual object, either statically or dynamically, without affecting the behaviour of other objects from the same class

Related posts:

Decorator Pattern for IO

When to Use the Decorator Pattern?

查看更多
冷夜・残月
6楼-- · 2019-01-01 03:26

your question is when to know:

i do not first code with visitor pattern. i code standard and wait for the need to occur & then refactor. so lets say you have multiple payment systems that you installed one at a time. At checkout time you could have many if conditions (or instanceOf) , for example :

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

now imagine i had 10 payment methods, it gets kind of ugly. So when you see that kind of pattern occuring visitor comes in handly to seperate all that out and you end up calling something like this afterwards:

new PaymentCheckoutVistor(paymentType).visit()

You can see how to implement it from the number of examples here, im just showing you a usecase.

查看更多
千与千寻千般痛.
7楼-- · 2019-01-01 03:29

Based on the excellent answer of @Federico A. Ramponi.

Just imagine you have this hierarchy:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

What happen if you need to add a "Walk" method here? That will be painful to the whole design.

At the same time, adding the "Walk" method generate new questions. What about "Eat" or "Sleep"? Must we really add a new method to the Animal hierarchy for every new action or operation that we want to add? That's ugly and most important, we will never be able to close the Animal interface. So, with the visitor pattern, we can add new method to the hierarchy without modifying the hierarchy!

So, just check and run this C# example:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
查看更多
登录 后发表回答