Patrones de diseño de comportamiento: Visitor

Intención del patrón

  • Representar una operación a ejecutarse en los elementos de una estructura de objetos. Visitor permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.
  • La técnica clásica para recuperar información.
  • Hacer lo correcto en función del tipo de dos objetos.
  • Duplicar el envío.

Ejemplo de problema

Muchas operaciones distintas y sin relación necesitan ser ejecutadas en una estructura heterogénea de objetos (o nodos). Se desea evitar ensuciar las clases con estas operaciones. Además, no se quiere tener que averiguar el tipo de cada nodo y convertirlo (cast) al tipo correcto antes de ejcutar la operación deseada.

Discusión

El propósito princiapl de Visitor es abstraer la funcionalidad que puede ser aplicada a una jerarquía de objetos (elementos). La propuesta alienta el diseño de clases Element livianas, porque la funcionalidad de proceso es quitada de su lista de responsabilidades. La nueva funcionalidad puede ser fácilmente agregada a la jerarquía de herencia original mediante la creación de una nueva subclase Visitor.
Visitor implementa la "duplicación de envío". Los mensajes OO generalmente manifiesta "mensajes únicos", la operación que es ejecutada depende de: el nombre de la solicitud y el tipo del receptor. En la duplicación de envío la operación ejecutada depende de: el nombre de la solicitud y el tipo de DOS receptores (el tipo del Visitor y el tipo del elemento "visitado").
La implementación procede de la siguiente manera: Crear una jerarquía de clases Visitor que defina un método abstracto visit() en la clase abstracta base por cada clase derivada concreta en la jerarquía de nodos (elementos). Cada método visit() acepta un solo argumento, una referencia a una clase derivada del Element original.
Cada operación que debe ser soportada es modelada con una clase concreta derivada de la jerarquía Visitor. Los métodos Visit() declarados en la clase base Visitor son ahora definidos en cada subclase derivada mediante la asignación del código del "tipo de solicitud y conversión" en la implementación original del método apropiado sobrescrito Visit().
Agregar un único método virtual Accept() a la clase base de la jerarquía Element. Éste método es definido para recibir un único argumento, una referencia a la clase base abstracta de la jerarquía Visitor.
Cada clase derivada concreta de la jerarquía Element implementa el método Accept() mediante el simple llamado al método Visit() de la instancia que fue pasada de la clase concreta derivada de la jerarquía Visitor, pasándole como argumento su propia referencia (this).
Todo está configurado para los Elements y Visitors. Cuando el cliente necesita que una operación sea ejecutada, crea una instancia del objeto Visitor, llama al método Accept() en cada objeto Element y pasa el objeto Visitor.
El método Accpet() causa que el flujo de control encuentre la correcta subclase de Element. Entonces, cuando el método Visit() es invocado, el flujo de control es direccionado hacia la subclase correcta de Visitor. El método Accept() realiza el envío, además, el método Visit() también lo hace, por lo que se produce la "duplicación de envío".
El patrón Visitor hace que agregar nuevas operaciones resulte fácil (simplemente se debe agregar una nueva clase derivada de Visitor). Pero, si las subclases en la jerarquía de Element no son estables, mantener en sincronía las subclases de Visitor requiere un esfuerzo que no vale la pena.
Una conocida objeción a este patrón es que representa una regresión a una descomposición funcional (separa los algoritmos de las estructuras de datos). Mientras que esta es una interpretación legítima, quizás una mejor perspectiva sea el logro de promover comportamientos no tradicionales hacia objetos con pleno estado.


Estructura

La jerarquía Element es instrumentada con "adaptador de método universal". La implementación de Accpet() en cada clase derivada de Element es siempre la misma. Pero (ésta no puede ser movida a la clase base Element y heredada por todas las clases derivadas porque una referencia a this en la clase Element siempre lleva al tipo base Element.




CUando el método polimórfico FisrtDispatch() es llamado en un objeto abstracto First, el tipo concreto de ese objeto es "recuperado". Cuando el método polimórfico SecondDispatch() es llamado en el objeto abstracto Second, su tipo concreto es "recuperado". La aplicación de la apropiada funcionalidad para este par de tipos puede ser ejecitada ahora.




Ejemplo

El patrón Visitor representa una operación a ejecutarse en los elementos de una estructura de objetos sin cambiar las clases en las cuales opera. Este patrón puede ser observado en la operación de una compañía de taxis. Cuando una persona llama a una compañía de taxis (acepta un "visitante"), la compañía envía un taxi al cliente. Al entrar en el taxi, el cliente (o visitante), ya no está en control de su propia transportación, sino que lo está el conductor del taxi.




Check list


  1. Confirmar que la jerarquía actual (conocida como la jerarquía Element) será bastante estable y que la interfaz pública de éstas clases son suficiente para el acceso que las clases Visitor requerirán. Si estas condiciones no están dadas, entonces el patrón no se ajustará bien.
  2. Crear una clase base Visitor con un método Visit( ElementXXX ) por cada tipo de subclase Element.
  3. Agregar un método Accept( Visitor ) a la jerarquía Element. La implementación en cada clase derivada es siempre la misma ( Accept( Visitor v ) { v.Visit( this ); } ). Debido a la dependencia cíclica, la declaración de las clases Element y Visitor necesitarán ser entrelazadas.
  4. La jerarquía Element está acoplada sólo a la clase base Visitor, pero la jerarquía Visitor está acoplada a cada clase derivada de Element. Si la estabilidad de la jerarquía Element es baja y la estabilidad de la jerarquía Visitor es alta, hay que considerar el intercambio de "roles" en ambas jerarquías.
  5. Crear una clase derivada de Visitor por cada "operación" a ejecutarse en objetos Element. La implementaciones de Visit() se basarán el la interfaz pública de Element.
  6. El cliente crea objetos Visitor y pasa cada uno a objetos Element llamando a Accept().

Reglas prácticas

  • El árbol abstracto de sintaxis de Interpreter es un Composite (para ello, Iterator y Visitor también son aplicables).
  • Iterator puede recorrer a Composite. Visitor puede aplicar una operación sobre Composite.
  • El patrón Visitor es como Command pero más potente ya que Visitor puede iniciarse apropiadamente para cualquier tipo de objeto que se encuentre.
  • El patrón Visitor es la técnica clásica para recuperar información de tipo perdida sin recurrir a conversiones dinámicas.

Notas

El informe de noviembre de 2000 de JavaPro tenía un artículo escrito por James Cooper al respecto del patrón Visitor. El sugiere que "convertir las tablas en nuestro modelo orientado a objeto y crear una clase externa para actuar sobre los datos en otras clases... mientras que estos no parece limpio... hay muy buenas razones para hacerlo".
Su principal ejemplo. Supongamos que se posee una jerarquí de Empleado-Ingeniero-Jefe. Todos ellos disfrutan de un día normal de vacaciones según la política. Pero los jefes también participan de un programa de días "bonus" de vacaciones. Como resultado, la interfaz de la clase Jefe, es diferente a la de la clase Ingeniero. No podemos, polimórficamente, recorrer una organización como un Composite y calcular un total de días de días remanentes de vacaciones para toda la organización. "El Visitor se hace más útil cuando hay gran cantidad de clases con diferentes interfaces y nosotros queremos encapsular como obtener datos de esas clases".

Sus beneficios incluyen:

  • Agrega funciones a librerías de clases, las cuales inclusive no se posee el código fuente o no se puede modificar.
  • Obtener información de una colección dispar de clases no relacionadas y usarla para presentar el resultado de un cálculo global al usuario del programa.
  • Reunir las operaciones relacionadas en una sola clase en lugar de obligarle a cambiar o derivar clases para agregar esas operación.
  • Colaborar con el patrón Composite.
Visitor no es bueno para la situación donde las clases "visitadas" no son estables. Cada vez que una nueva clase derivada de la jerarquía Composite es agregada, cada clase derivada de Visitor debe ser modificada.


Ejemplo de código en C#

using System;
using System.Collections;

class MainApp
{
  static void Main()
  {
    // Setup structure 
    ObjectStructure o = new ObjectStructure();
    o.Attach(new ConcreteElementA());
    o.Attach(new ConcreteElementB());

    // Create visitor objects 
    ConcreteVisitor1 v1 = new ConcreteVisitor1();
    ConcreteVisitor2 v2 = new ConcreteVisitor2();

    // Structure accepting visitors 
    o.Accept(v1);
    o.Accept(v2);

    // Wait for user 
    Console.Read();
  }
}

// "Visitor" 
abstract class Visitor
{
  public abstract void VisitConcreteElementA(
    ConcreteElementA concreteElementA);
  public abstract void VisitConcreteElementB(
    ConcreteElementB concreteElementB);
}

// "ConcreteVisitor1" 
class ConcreteVisitor1 : Visitor
{
  public override void VisitConcreteElementA(
    ConcreteElementA concreteElementA)
  {
    Console.WriteLine("{0} visited by {1}",
      concreteElementA.GetType().Name, this.GetType().Name);
  }

  public override void VisitConcreteElementB(
    ConcreteElementB concreteElementB)
  {
    Console.WriteLine("{0} visited by {1}",
      concreteElementB.GetType().Name, this.GetType().Name);
  }
}

// "ConcreteVisitor2" 
class ConcreteVisitor2 : Visitor
{
  public override void VisitConcreteElementA(
    ConcreteElementA concreteElementA)
  {
    Console.WriteLine("{0} visited by {1}",
      concreteElementA.GetType().Name, this.GetType().Name);
  }

  public override void VisitConcreteElementB(
    ConcreteElementB concreteElementB)
  {
    Console.WriteLine("{0} visited by {1}",
      concreteElementB.GetType().Name, this.GetType().Name);
  }
}

// "Element" 
abstract class Element
{
  public abstract void Accept(Visitor visitor);
}

// "ConcreteElementA" 
class ConcreteElementA : Element
{
  public override void Accept(Visitor visitor)
  {
    visitor.VisitConcreteElementA(this);
  }

  public void OperationA()
  {
  }
}

// "ConcreteElementB" 
class ConcreteElementB : Element
{
  public override void Accept(Visitor visitor)
  {
    visitor.VisitConcreteElementB(this);
  }

  public void OperationB()
  {
  }
}

// "ObjectStructure" 
class ObjectStructure
{
  private ArrayList elements = new ArrayList();

  public void Attach(Element element)
  {
    elements.Add(element);
  }

  public void Detach(Element element)
  {
    elements.Remove(element);
  }

  public void Accept(Visitor visitor)
  {
    foreach (Element e in elements)
    {
      e.Accept(visitor);
    }
  }
}
ConcreteElementA visited by ConcreteVisitor1
ConcreteElementB visited by ConcreteVisitor1
ConcreteElementA visited by ConcreteVisitor2
ConcreteElementB visited by ConcreteVisitor2

2 comentarios:

Alejandro Martinez dijo...

Buenisimo!

Antonio Vidal dijo...

Cuando se traduce un por es interesante poner la fuente.

Publicar un comentario

Muchas gracias por leer el post y comentarlo.

 
Copyright 2009 Programación SOLIDa
BloggerTheme by BloggerThemes | Design by 9thsphere