martes, 5 de mayo de 2015

Visitor

Visitor

Propósito

Permite incluir nuevos métodos a una clase sin tener que modificarla.

Motivación

Permite incluir nuevos métodos a una clase sin tener que modificar a gran escala las clases existentes, cuando se dispone de una estructura jerárquica en la que clases padre y clases hoja comparten métodos en común.

Aplicabilidad

Recomendado para: 
  • Estructuras jerárquicas (arboles).
  • Muchas clases poco relacionadas entre sí.
  • Estructura de objetos con diferentes interfaces y posibilidad de ampliación.
  • Estructura con altas probabilidades de incluir de nuevos métodos.
  • Compiladores, intérpretes.
No recomendado para:
  • Sistemas con cambios constantes en estructura global.
  • Estructuras poco jerárquicas.
Estructura

Participantes
  • Visitor. Declara una operación visitar para cada clase de operación ConcreteElement de la estructura de objetos.
  • ConcreteVisitor. Implementa cada operación declarada por Visitor.
  • Element. Define una operación que le permite aceptar la visita de un Visitor.
  • ConcreteElement. Implementa la operación Aceptar que se limita a invocar su correspondiente método del Visitor.
  • ObjectStructure. Puede enumerar sus elementos y puede proporcionar una interfaz de alto nivel para permitir al Visitor visitar sus elementos.
Colaboraciones
El cliente visitará a cada elemento de la estructura de objetos con un visitante concreto (previamente creado por él). Cuando se visita un elemento, éste llama a la operación del visitante correspondiente a su clase. El objeto se pasa como argumento para permitir al visitante el acceso a su estado.

Consecuencias

Es fácil añadir nuevas operaciones a un programa utilizando Visitantes, ya que el visitante contiene el código en lugar de cada una de las clases individuales. Además, los visitantes pueden recoger las operaciones relacionadas en una sola clase en lugar de obligar a cambiar o derivar clases para agregar estas operaciones. Esto puede hacer al programa más sencillo de escribir y mantener.

El patrón Visitante es útil cuando se desea encapsular buscando datos desde un número de instancias de varias clases. Los patrones de diseño sugieren que el visitante puede proporcionar una funcionalidad adicional a una clase sin cambiarla. Pero es más práctico decir que un visitante puede agregar funcionalidad a una colección de clases y encapsular los métodos que utiliza.

Se pueden tener problemas con la encapsulación, la solución para ello es que como los atributos de los elementos no pueden ser públicos se hace que todo este en un mismo paquete, es decir, visibilidad de paquete.

Como se comenta anteriormente, es difícil añadir nuevas clases de elementos, ya que obliga a cambiar a los visitantes.

Facilita la acumulación de estado, es decir, acumular resultados.

Implementación

Al implementar el patrón Visitor debemos tener en cuenta:
  • El cliente crea una instancia de un ConcreteVisitor y lo utiliza para recorrer su estructura.
  • Las clases visitadas por un mismo Visitor pueden no estar relacionadas entre sí a través de la herencia.
  • El Visitor facilita añadir nuevas operaciones.
  • Agrupa operaciones relacionas y separa las que no lo están.
  • Añadir nuevas clases a la estructura resulta muy costoso.
Código de ejemplo

/*
 * Esta es la superclase de una jerarquía que permite representar expresiones
 * aritméticas simples y sobre la que deseamos definir visitantes.
 */
 package expresion;
public abstract class Expresion {
  abstract public void aceptar(VisitanteExpresion v);
}
 package expresion;
public class Constante extends Expresion {
  public Constante(int valor) { _valor = valor; }
  public void aceptar(VisitanteExpresion v) { v.visitarConstante(this); }
  int _valor;
}
 package expresion;
public class Variable extends Expresion {
  public Variable(String variable) { _variable = variable; }
  public void aceptar(VisitanteExpresion v) { v.visitarVariable(this); }
  String _variable;
}
 package expresion;
public abstract class OpBinaria extends Expresion {
  public OpBinaria(Expresion izq, Expresion der) { _izq = izq; _der = der; }
  Expresion _izq, _der;
}
package expresion;
public class Suma extends OpBinaria {
  public Suma(Expresion izq, Expresion der) { super(izq, der); }
  public void aceptar(VisitanteExpresion v) { v.visitarSuma(this); }
}
 package expresion;
public class Mult extends OpBinaria {
  public Mult(Expresion izq, Expresion der) { super(izq, der); }
  public void aceptar(VisitanteExpresion v) { v.visitarMult(this); }
}

/*
 * Esta es la clase abstracta que define la interfaz de los visitantes
 * de la jerarquía Expresion -- en realidad, utilizamos una interfaz Java
 * dado que todos los métodos son abstractos.
 */

package expresion;
public interface VisitanteExpresion {
  public void visitarSuma(Suma s);
  public void visitarMult(Mult m);
  public void visitarVariable(Variable v);
  public void visitarConstante(Constante c);
}

/**
 * Uno de los posibles visitantes de las Expresiones es un pretty printer
 * que convierte a cadena de caracteres la expresión aritmética. El algoritmo
 * usado no optimiza el uso de paréntesis... El resultado se acumula en
 * el atributo privado _resultado, pudiéndose acceder a éste desde el exterior
 * mediante el método obtenerResultado()
 */

package expresion;
public class PrettyPrinterExpresion implements VisitanteExpresion {

  // visitar la variable en este caso es guardar en el resultado la variable
  // asociada al objeto... Observe que accedemos al estado interno del objeto
  // confiando en la visibilidad de paquete...

  public void visitarVariable(Variable v) {
                _resultado = v._variable;
  }

  public void visitarConstante(Constante c) {
                 _resultado = String.valueOf(c._valor);
}

  // Dado que el pretty-printer de una operación binaria es casi idéntica,
  // puedo factorizar parte del código con este método privado...

  private void visitarOpBinaria(OpBinaria op, String pOperacion) {
                  op._izq.aceptar(this);
                 String pIzq = obtenerResultado();

                op._der.aceptar(this);
                String pDer = obtenerResultado();

                _resultado = "(" + pIzq + pOperacion + pDer + ")";
  }

  // Por último la visita de la suma y la mult se resuelve mediante el método
  // privado que se acaba de mencionar...

 public void visitarSuma(Suma s) {
                visitarOpBinaria(s, "+");
  }
  public void visitarMult(Mult m) {
                visitarOpBinaria(m, "*");
  }

  // El resultado se almacena en un String privado. Se proporciona un método
  // de acceso público para que los clientes del visitante puedan acceder
  // al resultado de la visita

  public String obtenerResultado() {
                return _resultado;
   }
  private String _resultado;
}

import expresion.*;
class Main {
  static public void main(String argv[]) {
    // Construcción de una expresión (a+5)*(b+1)
    Expresion expresion = new Mult( new Suma( new Variable("a"),
                                              new Constante(5) ),
                                    new Suma( new Variable("b"),
                                              new Constante(1) ));

     // Pretty-printing...
     PrettyPrinterExpresion pretty = new PrettyPrinterExpresion();
     expresion.aceptar(pretty);    

    // Visualizacion de resultados
    System.out.println("Resultado: " + pretty.obtenerResultado());
  }
}

Usos conocidos

Este patrón es ampliamente utilizado en intérpretes, compiladores y procesadores de lenguajes, en general.

Patrones relacionados

Composite: Los Visitor se pueden aplicar sobre objetos de estructuras definidas por Composite.
Interprete:  Los Visitor pueden ser aplicados para realizar la interpretación.

Referencias

  • Design Patterns Elements of Reusable Object-Oriented Software, GoF. http://kybele.escet.urjc.es/documentos/SI/Patrones/23_Visitor.ppt
  • http://dmi.uib.es/~yuhua/APOO07-08/Presentation/visitor.pdf


No hay comentarios:

Publicar un comentario