2009-02-03 24 views
7

Deseo saber si el siguiente es un uso aceptable del patrón de visitante. Me siento un poco incómodo al regresar de una llamada de Aceptar() o de Visita(): ¿es este un uso apropiado de este patrón y, de no ser así, por qué no?Uso del patrón de visitante con genéricos en C#

Nota: Disculpas por el ejemplo de código largo, parece necesario conseguir a través de lo que estoy haciendo como visitante siempre parece estar un poco involucrado ...

interface IAnimalElement<T> 
{ 
    T Accept(IAnimalVisitor<T> visitor); 
} 

interface IAnimalVisitor<T> 
{ 
    T Visit(Lion lion); 
    T Visit(Peacock peacock); 
    T VisitZoo(List<Animal> animals); 
} 

abstract class Animal 
{ 
    public int Age { get; protected set; } 
} 

class Lion : Animal, IAnimalElement<int> 
{ 
    public Lion(int age) 
    { 
     Age = age; 
    } 

    public int Accept(IAnimalVisitor<int> visitor) 
    { 
     return visitor.Visit(this); 
    } 
} 

class Peacock : Animal, IAnimalElement<int> 
{ 
    public Peacock(int age) 
    { 
     Age = age; 
    } 

    public int Accept(IAnimalVisitor<int> visitor) 
    { 
     return visitor.Visit(this); 
    } 
} 

class AnimalAgeVisitor : IAnimalVisitor<int> 
{ 
    public int TotalAge { get; private set; } 

    int IAnimalVisitor<int>.Visit(Lion lion) 
    { 
     TotalAge += lion.Age; 
     return lion.Age; 
    } 

    int IAnimalVisitor<int>.Visit(Peacock peacock) 
    { 
     TotalAge += peacock.Age + 10; 
     return peacock.Age + 10; // peacocks ages are always -10y, correct. 
    } 

    public int VisitZoo(List<Animal> animals) 
    { 
     // Calculate average animal age. 

     int sum = 0; 
     int count = 0; 
     foreach (IAnimalElement<int> animal in animals) 
     { 
      sum += animal.Accept(this); 
      ++count; 
     } 

     return count == 0 ? 0 : sum/count; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Animal> animals = new List<Animal>() { new Lion(10), 
      new Lion(15), new Peacock(3), new Lion(2), new Peacock(9) }; 

     AnimalAgeVisitor visitor = new AnimalAgeVisitor(); 

     Console.WriteLine("Average age = {0}, Total age = {1}", 
      visitor.VisitZoo(animals), visitor.TotalAge); 
    } 
} 
+0

Oh, por cierto, debería utilizar totalmente el atributo '[DebuggerStepThrough]' en sus métodos 'Accept'. –

Respuesta

3

Bueno, esto me parece que la implementación está un poco enredada.

Haga que sus métodos de Visita y Aceptar dejen de ser válidos y rastree todo el estado en el objeto de Visitante. Interrújalo al final.

o ...

Tienes Visita y Aceptar devuelve un estado en curso y aceptar un estado en curso entrante de una manera funcional.

Si opta por la segunda opción, no estoy realmente seguro de que se necesite un objeto o patrón de visitante, sino que puede usar un iterador, una función y algún estado transitorio.

1

Es bastante común. No sé si puede hacerlo en C#, pero en Java Es normal dejar el método Accept genérico, así que lo que se devuelve es decidido por el visitante no la visitee:

interface IAnimalElement 
{ 
    <T> T Accept(IAnimalVisitor<T> visitor); 
} 


interface IAnimalVisitor<T> 
{ 
    T Visit(Peacock animal); 
    ... 
} 

Para los procedimientos, una IAnimalVisitor<Void> regresar null puede ser utilizado.

1

El método de aceptación visitable no debe devolver nada. La aceptación solo debe indicar al visitante qué visitar después o durante la visita.

1

Respuesta corta: No veo ningún problema para exponer un IVisitor que devuelve un parámetro genérico.
Ver FxCop rules.

Luego, permite utilizar diferentes IVisitor, cada uno con un valor diferente.

Sin embargo, en su caso , Visitador no es útil, ya que cada animal tiene la Edad propiedad por lo que todo se puede hacer con Animal o un nuevo IAnimal interfaz.

alternativa es usar multiple-dispatch a costa de perder Typing Strong.

Utilice un Visitor pattern cuando se desea reemplazar (o evitar escribir) un interruptorcomo éste:

IAnimal animal = ...; 
switch (animal.GetType().Name) 
{ 
    case "Peacock": 
    var peacock = animal as Peacock; 
    // Do something using the specific methods/properties of Peacock 
    break; 
    case "Lion": 
    var peacock = animal as Lion; 
    // Do something using the specific methods/properties of Lion 
    break; 
    etc... 
} 

o el anidado if-then-else equivalente.

Su propósito es encaminar la instancia a la rutina correspondiente para su tipo mediante el uso de polimorfismo y luego evitar feas if-then-else interruptor de encendido/declaraciones y Manual echa. Además, ayuda a disminuir acoplamiento entre código no relacionado.

Alternativa a eso es agregar un método virtual en el árbol de clases para visitar. Sin embargo, a veces no es posible o deseable:

  • clase visitable código no modificable (no de su propiedad, por ejemplo)
  • código de clase visitable no relacionada con la visita de código (añadiéndolo en clase significaría la reducción de la cohesión de la clase).

Es por eso que a menudo se utiliza para recorrer un árbol de objetos (nodos html, tokens lexer, etc ...). Visitante patrón implica las siguientes interfaces:

  • IVisitor

    /// <summary> 
    /// Interface to implement for classes visiting others. 
    /// See Visitor design pattern for more details. 
    /// </summary> 
    /// <typeparam name="TVisited">The type of the visited.</typeparam> 
    /// <typeparam name="TResult">The type of the result.</typeparam> 
    public interface IVisitor<TVisited, TResult> : IVisitor where TVisited : IVisitable 
    { 
        TResult Visit(TVisited visited); 
    } 
    
    /// <summary> 
    /// Marking interface. 
    /// </summary> 
    public interface IVisitor{} 
    
  • IVisitable

    /// <summary> 
    /// Interface to implement for classes visitable by a visitor. 
    /// See Visitor design pattern for more details. 
    /// </summary> 
    /// <typeparam name="TVisitor">The type of the visitor.</typeparam> 
    /// <typeparam name="TResult">The type of the result.</typeparam> 
    public interface IVisitable<TVisitor, TResult> : IVisitable where TVisitor : IVisitor 
    { 
        TResult Accept(TVisitor visitor); 
    } 
    
    /// <summary> 
    /// Marking interface. 
    /// </summary> 
    public interface IVisitable {} 
    

Implementación de Aceptar en cada IVisitable debe llamar al Visitar (esto).

Cuestiones relacionadas