2010-04-08 13 views
63

Estoy realmente confundido sobre el patrón de visitante y sus usos. Realmente no puedo visualizar los beneficios de usar este patrón o su propósito. Si alguien pudiera explicar con ejemplos si es posible, sería genial.Propósito del patrón de visitante con los ejemplos

+6

¿Revisó Wikipedia sobre el tema? http://en.wikipedia.org/wiki/Visitor_pattern ¿Qué partes exactamente no están claras? ¿O estás buscando ejemplos del mundo real? – BalusC

Respuesta

51

Había una vez ...

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

luego te das cuenta que le gustaría ser capaz de filtrar la colección de la biblioteca por otros géneros. Podrías seguir agregando nuevos métodos getter. O podrías usar Visitantes.

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; } 
} 

Separa los datos del algoritmo. Descargue el algoritmo a implementaciones de visitante. Agregue funcionalidad al crear más visitantes, en lugar de modificar constantemente (e hinchar) la clase que contiene los datos.

+29

Lo siento, este no es realmente un buen ejemplo para el patrón de visitante demasiado simplista. Uno de los principales mecanismos del patrón visitante, la selección de la funcionalidad a través del tipo (doble despacho) del elemento visitado no se muestra -1 –

+0

Después de tomar un curso de compiladores, también me he dado cuenta de lo inútil que es este ejemplo. –

+3

@HaraldScheirich Los visitantes pueden elegir o no la funcionalidad por tipo.He encontrado que los visitantes son extremadamente útiles incluso sin eso. – DJClayworth

6

Proporciona otra capa de abstracción. Reduce la complejidad de un objeto y lo hace más modular. Clasificar como usar una interfaz (la implementación es completamente independiente y a nadie le importa cómo se hace simplemente que se haga).

Ahora nunca lo he usado, pero sería útil para: Implementar una función particular que necesita ser hecho en diferentes subclases, ya que cada una de las subclases necesita implementarlo de diferentes maneras, otra clase implementaría todas las funciones. Un poco como un módulo, pero solo para una colección de clases. Wikipedia tiene una muy buena explicación: http://en.wikipedia.org/wiki/Visitor_pattern Y su ejemplo ayuda a explicar lo que estoy tratando de decir.

Espero que ayude a aclararlo un poco.

EDIT ** Lo siento, me vinculé a wikipedia por su respuesta, pero realmente tienen un ejemplo decente :) No tratando de ser ese tipo que dice ir a buscarlo usted mismo.

+0

Esta explicación suena como el patrón de estrategia – Waclock

1

Es separar la manipulación de datos de los datos reales. Como beneficio adicional, puede reutilizar la misma clase de visitante para toda la jerarquía de sus clases, lo que de nuevo le evita transportar los algoritmos de manipulación de datos que son irrelevantes para sus objetos reales.

158

Así que probablemente hayas leído un millón de explicaciones diferentes del patrón de visitante, y probablemente sigas diciendo "¡pero cuándo lo usarías!"

Tradicionalmente, los visitantes se utilizan para implementar pruebas de tipo sin sacrificar la seguridad de tipo, siempre y cuando sus tipos estén bien definidos por adelantado y conocidos de antemano. Digamos que tenemos un par de clases de la siguiente manera:

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

Y digamos que creamos un Fruit[]:

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

Quiero particionar la lista de las tres listas, cada una contiene naranjas, manzanas, o Plátanos. ¿Como lo harias? Bueno, la solución fácilsería un tipo-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); 
} 

Funciona, pero hay un montón de problemas con este código:

  • Para empezar, su fea.
  • No es seguro para tipos, no captaremos errores de tipo hasta el tiempo de ejecución.
  • No es mantenible. Si agregamos una nueva instancia derivada de Fruit, debemos realizar una búsqueda global para cada lugar que realice una prueba de tipo de fruta, de lo contrario, podríamos omitir los tipos.

El patrón de visitante resuelve el problema elegantemente. Empezar modificando nuestra clase de fruta de base:

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); } } 

Parece que estamos pegar código copia, pero tenga en cuenta las clases derivadas son todos los que llaman diferentes sobrecargas (las llamadas AppleVisit(Apple), las llamadas BananaVisit(Banana), y así sucesivamente) .

implementar el visitante:

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); } 
} 

ya se puede particionar sus frutos sin una prueba de tipo:

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); 

Esto tiene las ventajas de:

  • ser relativamente limpia, código fácil de leer
  • Tipo de seguridad, los errores de tipo se capturan en tiempo de compilación.
  • Mantenibilidad. Si agrego una eliminación de una clase concreta de Frutas, podría modificar mi interfaz IFruitVisitor para manejar el tipo en consecuencia, y el compilador encontrará inmediatamente todos los lugares donde implementamos la interfaz para que podamos hacer las modificaciones apropiadas.

Dicho esto, los visitantes suelen ser una exageración, y tienen una tendencia extremadamente complicar las API, y puede ser muy engorroso para definir un nuevo visitante para cada nuevo tipo de comportamiento.

Por lo general, los patrones más simples como la herencia se deben utilizar en lugar de los visitantes. Por ejemplo, en principio, podría escribir una clase como:

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; } 
} 

Funciona, pero ¿cuál es la ventaja con respecto a esta modificación trivial:

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

Por lo tanto, se debe utilizar los visitantes cuando se mantienen las siguientes condiciones:

  • Tiene un conjunto de clases bien definido y conocido que se visitará.

  • Las operaciones en dichas clases no están bien definidas o conocidas de antemano. Por ejemplo, si alguien está consumiendo su API y desea brindar a los consumidores una forma de agregar nuevas funciones ad-hoc a los objetos. También son una forma conveniente de extender clases selladas con funciones ad-hoc.

  • Realiza operaciones de una clase de objetos y desea evitar las pruebas de tipo de tiempo de ejecución. Este suele ser el caso cuando recorre una jerarquía de objetos dispares que tienen propiedades diferentes.

No utilice los visitantes cuando:

  • Usted apoya las operaciones de una clase de objetos cuyos tipos de derivados no se conocen de antemano.

  • Las operaciones en objetos están bien definidas de antemano, especialmente si pueden heredarse de una clase base o definirse en una interfaz.

  • Es más fácil para los clientes agregar nuevas funcionalidades a las clases que usan herencia.

  • Está atravesando una jerarquía de objetos que tienen las mismas propiedades o interfaz.

  • Quiere una API relativamente simple.

+6

Excepto que el visitante no está cerrado por modificación (principio abierto/cerrado). Cualquier fruta nueva debe tener un nuevo método. Es mejor que el visitante solo tenga un método 'Visit (Fruit fruit)' y una implementación concreta que asigne cada fruta a un método específico. (Para que otras clases de visitantes puedan ampliar la base de concreto) – jgauffin

+4

@jgauffin una de las consecuencias formales del patrón Visitor es que cada vez que agrega un nuevo objeto que se puede visitar, se crea un nuevo método, por lo que una infracción de Abierto/Cerrado está implícito en las consecuencias de este patrón. Tener un método Visit() que tenga la resolución de tipo ofrece toneladas de desventajas, que incluyen no permitir IDE, compiladores, RTE, etc. para validar las implementaciones. – Fleep

2

Ejemplo de patrón de visitante. Libro, frutas & vegetales son elementos básicos de tipo "visitable" y hay dos "Visitantes", BillingVisitor & OfferVisitor cada uno de los visitantes tiene su propio propósito .Algo para calcular la factura y algo para calcular el las ofertas en estos elementos se encapsulan en el visitante respectivo y los Visitables (Elementos) permanecen iguales.

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);    
     } 
    } 


} 
1

Creo que el objetivo principal del patrón de visitante es su alta extensibilidad. La intuición es que has comprado un robot. El robot ya ha implementado funcionalidades básicas como ir adelante, girar a la izquierda, girar a la derecha, retroceder, elegir algo, hablar una fase, ...

Un día, quiere que su robot pueda ir a la oficina de correos para usted. Con todas estas funciones elementales, puede funcionar, pero debe llevar su robot a la tienda y "actualizar" su robot. El vendedor de la tienda no necesita modificar el robot, sino simplemente ponerle un nuevo chip de actualización a su robot y puede hacer lo que quiera.

Un día más, quiere que su robot vaya al supermercado. El mismo proceso, debe llevar su robot a la tienda y actualizar esta funcionalidad "avanzada". No es necesario modificar el robot en sí.

y así sucesivamente ...

Así que la idea del patrón de visitante es, teniendo en cuenta todas las funcionalidades elementales implementadas, puede utilizar patrón de visitante para agregar un número infinito de funcionalidades sofisticadas. En el ejemplo, el robot es su clase de trabajadores, y el "chip de actualización" son los visitantes. Cada vez que necesita una nueva "actualización" de funcionalidad, no modifica su clase de trabajador, pero agrega un visitante.

Cuestiones relacionadas