lunes, 26 de agosto de 2013

Visitor Design Pattern - Parte I

Introducción

En este artículo he decido escribir acerca de un patrón de diseño que es muy poco conocido y que puede dejarte con sentimientos encontrados en cuanto a que si conviene implementarlo o no, lo cual puede ser un indicador para evaluar mejor si la aplicación que le darás es válida.

Trataré de explicarlo paso a paso con la idea que de pueda ser útil para cualquier desarrollador principiante o no. Solo se requiere estar familiarizado con el paradigma de la orientación a objetos.

Hace un par de años empecé a escribir una librería para incluirla en uno de los proyectos que estaba trabajando para convertir expresiones de consultas con entidades a sql y xml, es el mejor uso que le he dado a este patrón desde que pude entender su utilidad y la relación que tenía con otros frameworks que procesan estructuras o listas de objetos para generar un resultado a partir de diferentes elementos de tipos de datos conocidos como por ejemplo las expressiones booleanas o las expressiones lambda que escribimos en .Net.

Dicha librería utiliza entidades que contienen metadata de la aplicación y para el problema que enfrento ahora necesito algo mucho más sencillo lo cual nos reune aquí.

El código de los ejemplos están con sintaxis del lenguaje C# pero perfectamente lo pueden aplicar en otros lenguajes como C++ o Java.

La definición
"En programación orientada a objetos, el patrón visitor es una forma de separar el algoritmo de la estructura de un objeto". Perfecto! Lo dejamos hasta ahí por el momento.

El problema a resolver
Si partimos del típico ejemplo de las figuras que definen su propia implementación del método dibujar heredado de una clase base “Figura” a como está a continuación:

public abstract class Figura
       {
              // Método para definir el código para dibujar la figura concreta
              public abstract void Dibujar();
       }

       public class Circulo : Figura
       {            
public override void Dibujar()
              {
                     // Implementación aquí
              }
       }

       public class Triangulo : Figura
       {
              public override void Dibujar()
              {
                     // Implementación aquí
              }
       }

       public class Cuadrado : Figura
       {
              public override void Dibujar()
              {
                     // Implementación aquí
              }
       }

Aplicando los conceptos de orientación a objetos como la abstracción, herencia y polimorfismo la tarea de dibujar cada figura se ve resuelta. Pero que tal si la estructura de estas figuras inicialmente no contaran con esa implementación y no se pudieran modificar por alguna razón por ejemplo: porque no contamos con el código fuente o porque son operaciones que realmente no deben ir dentro de esas clases.

Por lo tanto si quitamos el código del método dibujar de la clase figura (la definición) y lo ubicamos en otra parte de nuestra aplicación podriamos tener algo así (la forma menos aceptable por incluir la lógica dentro del foreach):

static void Main(string[] args)
{
              // Lista genérica de figuras
              List<Figura> figuras = new List<Figura>()
              {
new Circulo(),
                     new Triangulo(),
                     new Cuadrado(),
                     new Circulo()
              };

              // Iteración de los elementos de la lista
              foreach (Figura figura in figuras)
              {
                     if (figura is Circulo)
                     {
                           // Multiples líneas de código
// para para dibujar el círculo
                     }
                     else
                           if (figura is Triangulo)
                           {
                                  // Multiples líneas de código
                                  // para para dibujar el triángulo
                           }
                           else
                                  if (figura is Cuadrado)
                                  {
                                         // Multiples líneas de código
                                         // para para dibujar el cuadrado
                                  }
              }
        }

Una implementación más decente supondría mover el código que dibuja cada una de las figuras a métodos diferentes:

// Iteración de los elementos de la lista
              foreach (Figura figura in figuras)
              {
                     if (figura is Circulo)
                           Dibujar((Circulo)figura);
                     else
                           if (figura is Triangulo)
                                  Dibujar((Triangulo)figura);
                           else
if (figura is Cuadrado)
                                         Dibujar((Cuadrado)figura);
            }

Con los respectivos métodos:

private void Dibujar(Circulo figura)
       {
              // Implementación aquí
       }

       private void Dibujar(Triangulo figura)
       {
              // Implementación aquí
       }

       private void Dibujar(Cuadrado figura)
       {
              // Implementación aquí
       }

Aún así nos quedamos con la verificación de cada uno de los tipos de dato del objeto en curso “figura” por lo tanto es muy notable la dependencia que hay hacia esas clases, he aquí donde el patrón visitor puede ser aplicado para mejorar nuestro código, ojo con el hecho de que este patrón se aplica mayormente a estructura de datos bien conocidas en donde no se estarán creando nuevos tipos o por lo menos no como algo frecuente.

Los participantes del patrón

Ilustración de su interacción y relación

Visitor (Visitante):  Una interfaz que define una operación de visita para cada uno de los elementos de la estructura.
Concrete Visitor (Visitante concreto): Una clase con la implementación real de cada una de las operaciones de visita.
Element: Elemento base de la estructura que define una operación de aceptación de la visita.
Concrete Element: Elemento concreto que implementa el código del método de aceptación pasando el flujo hacia el Concrete Visitor directamente al método correspondiente del elemento visitado.

La idea simple es invocar un método Accept en los elementos definidos pasandole como parámetro una instancia del Visitor y dentro de este método Accept invocar el método sobrecargado (VisitConcreteElmentX) pasándole como parámetro la referencia del elemento que está siendo visitado.

Si está un poco confuso veamos este escenario aplicado al mundo real:

Un veterinario tiene que ir a una granja a curar animales y por cada animal que visite: caballo, vaca, oveja  él tiene que aplicar una cura diferente.

Considerando en nuestro ejemplo al veterinario en general como la persona que cura a los animales tenemos:

Visitor: Hace referencia al veterinario con lo cual podemos decir: los veterinarios curan vacas, caballos y ovejas.
Concrete Visitor: Es el médico veterinario (Concrete Visitor 1) o el curandero empírico (Concrete Visitor 2).
Element: La estructura es acerca de animales por lo tanto es "Animal".
Concrete Element: Animal específico osea "Vaca", "Caballo" u "Oveja".


Aplicando el patrón

Retomando el ejemplo de las figuras tendríamos que hacer lo siguiente:

Paso 1: Crear la interfaz del visitor sobrecargando un método para todos los tipos de datos de la estructura.
Nota: Es más común sobrecargar un método que utilizar nombres diferentes como en el diagrama.

public interface IVisitor
       {
              public abstract void Visit(Circulo figura);
              public abstract void Visit(Cuadrado figura);
              public abstract void Visit(Triangulo figura);
       }

Paso 2: Definir un método abstracto en la clase base de los elementos e implementarlo en las clases derivadas
aceptando la visita del visitor.

public abstract class Figura
       {
              public abstract void Accept(IVisitor visitor);
       }

       public class Circulo : Figura
       {
public override void Accept(IVisitor visitor)
              {
                     visitor.Visit(this);
              }
       }

       public class Cuadrado : Figura
       {
              public override void Accept(IVisitor visitor)
              {
                     visitor.Visit(this);
              }
       }

       public class Triangulo : Figura
       {
public override void Accept(IVisitor visitor)
              {
                     visitor.Visit(this);
              }
       }

Paso 3: Definir la clase visitor que tendrá la implementación concreta.

public class DibujanteVisitor : IVisitor
       {
              public virtual void Visit(Circulo figura)
              {
                     // Implementar código aquí
              }

              public virtual void Visit(Cuadrado figura)
              {
                     // Implementar código aquí
              }

              public virtual void Visit(Triangulo figura)
              {
                     // Implementar código aquí       
              }
       }

Paso 4: Refactorizar nuestro código para hacer uso de las nuevas clases.

      static void Main(string[] args)
      {
              // Lista genérica de figuras
              List<Figura> figuras = new List<Figura>()
              {
                     new Circulo(),
                     new Triangulo(),
                     new Cuadrado(),
                     new Circulo()
              };

              DibujanteVisitor visitor = new DibujanteVisitor();

              // Iteración de los elementos de la lista
              foreach (Figura figura in figuras)
              {
figura.Accept(visitor);
              }
        }

Y voilà! Ahora nuestro método ya no necesita conocer las clases Triángulo, Cuadrado y Círculo y así eliminamos esas dependencia y resolvemos el problema de una forma elegante.

Lo que sigue en la “Parte II”

La meta es crear un pequeño Api (Expression Builder) para escribir expresiones como el código abajo y generar las consultas dinámicamente utilizando el patrón visitor, la motivación es un escenario real que se justificará con la siguiente publicación, aquí un ejemplo de lo que buscamos lograr:

EntityExpression expression = new EntityExpression("Entity");
expression.Select("Field1", "Field2", "Field3", "Field4");
expression.Where("Field1", ConditionOperator.Equals, "Value  X");

Llegamos al final

Mientras se desarrolla la siguiente parte te gustaría compartir tus opiniones acerca de esto:

¿Consideras que este patrón entra en conflicto con otros principios del paradigma orientado a objetos?
¿Hay otro patrón o método propio que utilizarías para el ejemplo de las figuras?

Pueden dejar sus comentarios abajo y/o subscribirse al blog para tener la notificación de la segunda parte y también mientras tanto los invito a compartirlo.