Visitor (patrón de deseño)

Na Galipedia, a Wikipedia en galego.

En programación orientada a obxecto, o patrón de deseño visitor (visitante) é unha forma de separar o algoritmo da estrutura dun obxecto.

É un patrón de comportamento, que permite incluír novos métodos nunha clase sen que se teña que modificar.

Propósito[editar | editar a fonte]

Representa unha operación sobre os elementos dunha estrutura de obxectos. Permítenos definir unha nova operación, ou modificala, sen cambiar as clases dos elementos sobre os que opera. Proporciona unha forma fácil e sostible de executar accións nunha familia de clases.

Motivación[editar | editar a fonte]

Un compilador representa os programas como árbores de sintaxe abstracta, sobre os que executa operacións. Moitas operacións necesitan diferenciar distintos tipos de nodo na árbore como poden ser expresións, variables, etc....

O problema que nos atopamos é a dificultade de entende, manter, modificar e estender posto que cada clase ten a súa parte correspondente de cada unha das operacións.

Unha posible solucións sería agrupar as operacións relacionas de cada clase nun obxecto e pasarllo como argumento a cada nodo da árbore. A este obxecto chamariámoslle visitante (visitor) e o elemento que visita ten que “aceptalo” para que execute a operación para ese elemento. Na aceptación, o elemento envíalle ao visitante unha petición específicas para a súa clase con el mesmo como argumento.

Se queremos definir máis dunha clase visitante, temos que crear unha superclase abstracta e definir unha operación por cada tipo de nodo.

Aplicabilidade[editar | editar a fonte]

Úsase o patrón Visitor cando:

  • Unha estrutura obxectos contén moitas clases con diferentes interfaces e queremos realizar operacións sobre eses elementos que dependen da súa clase concreta.
  • Cando se necesita realizar diversas operacións, que non teñen porqué estar relacionadas, sobre os obxectos dunha xerarquía e non queremos “contaminar” o sobrecargar as clases coas ditas operacións. O patróns Visitor mantén xuntas as operacións nunha clase e se a estrutura esa compartida por varias aplicacións, permítenos poñer operacións só en aquelas aplicacións que as necesiten.
  • Se as clases da xerarquía non cambian, xa que se a estrutura cambiase non se podería aplicar, e queremos engadir con frecuencia novas operacións.

Estrutura[editar | editar a fonte]

Estrutura.

Participantes[editar | editar a fonte]

  • Visitante: Declara unha operación de visita para cada ElementoConcreto da estrutura. O nome e a sinatura da operación identifican á clase que envía a petición Visitar ó visitante. Con iso, permite ó visitante determinar a clase concreta do elemento que está sendo visitada para poder acceder ó elemento directamente.
  • VisitanteConcreto: Implementa as operacións do Visitante. Moitas veces este estado acumula resultados durante o recorrido da estrutura.
  • Elemento: Define unha operación “aceptar” que toma un visitante como argumento.
  • ElementoConcreto: Implementa unha operación “aceptar” que toma un visitante como argumento.

Colaboracións[editar | editar a fonte]

O cliente visitará a cada elemento da estrutura de obxectos cun visitante concreto (previamente creado por el). Cando se visita un elemento, este chama á operación do visitante correspondente a súa clase. O obxecto pásase como argumento para permitir ó visitante o acceso ó seu estado.

Colaboracións.

Consecuencias[editar | editar a fonte]

Se nun programa usamos visitantes, é moi fácil engadir novas operacións, xa que o visitante contén o código en lugar de cada unha das subclase individuais. Ademais, os visitantes poden conter as operacións relacionadas nunha soa clase, e así non ten que obrigar a cambiar ou derivar clases para agregar estas operacións. Grazas a isto, o programa é máis sinxelo de escribir e manter.

Os patróns de deseño suxiren que o visitante pode proporcionar unha funcionalidade adicional a unha clase sen cambiala, pero é máis práctico dicir que un visitante pode agregar funcionalidade a unha colección de clases e encapsular os métodos que utiliza.

É difícil engadir novas clases de elementos, xa que obrigar a cambiar ós visitantes.

Facilita a acumulación de estados, é dicir, acumular resultados.

Exemplo de implementación[editar | editar a fonte]

No seguinte exemplo, teremos unha xerarquía de expresións aritméticas simples sobre as que se desexa definir visitantes. un dos visitantes será a operación PrettyPrit que convirte a cadea de caracteres na expresión aritmética.

ExemploImplementacion.


/*
 * Esta é a superclase dunha xerarquía que permite representar expresións 
 * aritméticas simples e sobre a que desexamos 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 é a clase abstracta que define la interface dos visitantes
 * da xerarquía Expresion -- en realidade, utilizamos una interface Java
 * dado que tódolos 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);
}
 
/**
 * Un dos posibles visitantes das Expresiones é un pretty printer
 * que convirte a cadea de caracteres a expresión aritmética. O algoritmo
 * usado non optimiza o uso de parénteses... O resultado acumúlase no atributo privado
 * _resultado, podéndose acceder a este dende o exterior mediante o método obtenerResultado()
 */
 
package expresion;
public class PrettyPrinterExpresion implements VisitanteExpresion {
 
  // visitar a variable neste caso é gardar no resultado a variable
  // asociada ó obxecto... Observe que accedemos ó estado interno do obxecto
  // confiando na visibilidade de paquete...
 
  public void visitarVariable(Variable v) { 
	_resultado = v._variable; 
  }
 
  public void visitarConstante(Constante c) {
	 _resultado = String.valueOf(c._valor); 
}
 
  // Dado que o pretty-printer dunha operación binaria é case idéntica,
  // podo factorizar parte do 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 a visita da suma e a mult resólvese mediante o método
  // privado que se acaba de mencionar...
 
 public void visitarSuma(Suma s) { 
	visitarOpBinaria(s, "+");
  }
  public void visitarMult(Mult m) { 
	visitarOpBinaria(m, "*"); 
  }
 
  // O resultado almacénase nun String privado. Proporciónase un método
  // de acceso público para que os clientes do visitante poidan acceder
  // ó resultado da 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());
  }
}

Véxase tamén[editar | editar a fonte]

Bibliografía[editar | editar a fonte]

  • Design Patterns. Elements of Reusable Object-Oriented Software - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides - Addison Wesley (GoF- Gang of Four)