Usar patrón de visitante con jerarquía de objetos grandes

12

Contexto

He estado usando con una jerarquía de objetos (un árbol de expresión) un patrón de visitante "pseudo" (pseudo, ya que en él no se usa el envío doble):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Este diseño fue, sin embargo cuestionable, bastante cómodo ya que la cantidad de implementaciones de MyInterface es significativa (~ 50 o más) y no necesité agregar operaciones adicionales.

Cada implementación es única (es una expresión u operador diferente), y algunos son compuestos (es decir, nodos de operador que contendrán otros nodos de operador / hoja).

El recorrido se realiza actualmente llamando a la operación Aceptar en el nodo raíz del árbol, que a su vez llama Aceptar en cada uno de sus nodos secundarios, que a su vez ... y así sucesivamente ...

Pero ha llegado el momento en que necesito agregar una nueva operación , como una bonita impresión:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Básicamente veo dos opciones:

  • Mantener el mismo diseño, agregando un nuevo método para mi operación a cada clase derivada, a expensas de la capacidad de mantenimiento (no es una opción, en mi humilde opinión)
  • Use el patrón de visitante "verdadero", a expensas de la extensibilidad (no es una opción, ya que espero tener más implementaciones en el camino ...), con aproximadamente más de 50 sobrecargas del método de visita, cada una de las cuales coincide con una implementación específica ?

Pregunta

¿Recomendaría usar el patrón de visitante? ¿Hay algún otro patrón que pueda ayudar a resolver este problema?

T. Fabre
fuente
1
¿Quizás una cadena de decoradores sería más apropiada?
MattDavey
Algunas preguntas: ¿cómo difieren estas implementaciones? ¿Cuál es la estructura de la jerarquía? y es siempre la misma estructura? ¿siempre necesita atravesar la estructura en el mismo orden?
jk.
@MattDavey: ¿recomendaría tener un decorador por implementación y operación?
T. Fabre
2
@ T.Fabre es difícil saberlo. Hay más de 50 implementadores de MyInterface... ¿todas esas clases tienen una implementación única de DoSomethingy DoSomethingElse? No veo dónde está su clase visitante atraviesa realmente la jerarquía - se parece más a una facadeen el momento ..
MattDavey
También qué versión de C # es. tienes lambdas? o linq? a su disposición
jk.

Respuestas:

13

He estado usando el patrón de visitante para representar árboles de expresión en el transcurso de más de 10 años en seis proyectos a gran escala en tres lenguajes de programación, y estoy muy satisfecho con el resultado. Encontré un par de cosas que facilitaron la aplicación del patrón:

No utilice sobrecargas en la interfaz del visitante.

Ponga el tipo en el nombre del método, es decir, use

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

más bien que

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Agregue un método de "captura desconocida" a su interfaz de visitante.

Permitiría a los usuarios que no pueden modificar su código:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Esto les permitiría construir sus propias implementaciones IExpressiony IVisitoreso "comprende" sus expresiones mediante el uso de información de tipo de tiempo de ejecución en la implementación de su VisitExpressionmétodo general.

Proporcionar una implementación predeterminada de no hacer nada de la IVisitorinterfaz

Esto permitiría a los usuarios que necesitan lidiar con un subconjunto de tipos de expresión construir sus visitantes más rápido y hacer que su código sea inmune a que agreguen más métodos IVisitor. Por ejemplo, escribir un visitante que recolecte todos los nombres de variables de sus expresiones se convierte en una tarea fácil, y el código no se romperá incluso si agrega un montón de nuevos tipos de expresiones a su IVisitorposterior.

dasblinkenlight
fuente
2
¿Puedes aclarar por qué dices Do not use overloads in the interface of the visitor?
Steven Evers
1
¿Puedes explicar por qué no recomiendas usar sobrecargas? Leí en alguna parte (en oodesign.com, en realidad) que realmente no importa si uso sobrecargas o no. ¿Hay alguna razón específica por la que prefieres ese diseño?
T. Fabre
2
@ T.Fabre No importa en términos de velocidad, pero sí en términos de legibilidad. La resolución del método en dos de los tres lenguajes donde implementé esto ( Java y C #) requiere un paso en tiempo de ejecución para elegir entre las posibles sobrecargas, lo que hace que el código con una gran cantidad de sobrecargas sea un poco más difícil de leer. Refactorizar el código también se vuelve más fácil, porque elegir el método que desea modificar se convierte en una tarea trivial.
dasblinkenlight
@SnOrfus Por favor vea mi respuesta a T.Fabre arriba.
dasblinkenlight
@dasblinkenlight C # ahora ofrece una dinámica para permitir que el tiempo de ejecución decida qué método sobrecargado debe usarse (no en tiempo de compilación). ¿Todavía hay alguna razón por la que no usar la sobrecarga?
Tintenfiisch