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