Visitor (padrón de deseño)

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

É un padró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 entender, manter, modificar e estender posto que cada clase ten a súa parte correspondente de cada unha das operacións.

Unha posible solución sería agrupar as operacións relacionadas 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ífica 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 padró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 por que estar relacionadas, sobre os obxectos dunha xerarquía e non queremos “contaminar” ou sobrecargar as clases coas ditas operacións. O padróns Visitor mantén xuntas as operacións nunha clase e se a estrutura está compartida por varias aplicacións, permítenos poñer operacións só naquelas 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.
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 a 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 percorrido 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.
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 subclases 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 padró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 obrigan a cambiar os 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 converte a cadea de caracteres na expresión aritmética.

ExemploImplementacion.
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 converte 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)