Decorator (padrón de deseño)

O padrón de deseño Decorador (Decorator) responde á necesidade de engadir de forma dinámica responsabilidades ou funcionalidades a un obxecto. Desta maneira proporciónase unha alternativa flexible á extensión dunha clase. Tamén é coñecido como padrón envoltorio (Wrapper).

Exemplo visual do uso de decoradores sobre unha vista de texto.

Motivación editar

  • Ás veces deséxase engadir responsabilidades a obxectos individuais, non a toda unha clase. Por exemplo, se dispoñemos dunha ferramenta para crear interfaces de usuario, deberíamos poder engadir propiedades tales como bordos ou funcionalidades como barras de desprazamento a calquera compoñente da interface.
  • Nunha primeira aproximación, pódese pensar en engadir estas propiedades ou funcionalidades a través da herdanza. Herdar un bordo doutra clase engadiría esa cualidade a todas as instancias da subclase. Porén, isto produce que o modelo sexa inflexible (estático), xa que o cliente non pode controlar cando e como decorar o compoñente con esa propiedade.
  • Como solución, encapsúlanse dentro doutro obxecto as novas responsabilidades. O novo obxecto é denominado Decorador. Este axústase á interface do compoñente, de xeito que é transparente para o cliente. O decorador redirixe as peticións ao compoñente e, ademais, pode realizar accións adicionais antes ou despois da redirección. Deste modo, pódense engadir decoradores recursivamente, permitindo un número ilimitado de cualidades engadidas.
 
Exemplo de modelo
  • Como se pode ver no diagrama de clases, a interface decoradora implementa a interface do compoñente visual, redirixindo todos os métodos ao compoñente visual que encapsula.
  • As subclases decoradoras refinan os métodos do compoñente, engadindo responsabilidades. Como se ve no exemplo, o decorador de bordos debuxa un bordo, e o decorador da barra de desprazamento debúxase e engade esta mesma propiedade de desprazamento.
  • Os clientes do sistema non necesitan facer distinción entre os compoñentes visuais decorados e os sen decorar.

Aplicabilidade editar

  • Engadir ou eliminar responsabilidades entre obxectos de forma dinámica e transparente, é dicir, sen afectar a outros obxectos no proceso. Simplemente se decora o obxecto desexado coa funcionalidade que se lle quere engadir, ou se lle elimina ese obxecto se o que se desexa é eliminar esa funcionalidade a maiores.
  • Práctico cando a herdanza non é unha opción. Ás veces é posible ter un gran número de extensións independentes, producíndose unha gran cantidade de subclases para permitir todas as combinacións posibles. Tamén pode ser que unha definición de clase estea oculta ou non dispoñible para ser herdada.

Estrutura editar

 
Exemplo de modelo

Participantes editar

  • Compoñente (component)

Define a interface para os obxectos que poidan ter responsabilidades engadidas.

  • Compoñente concreto (ConcreteComponent)

Define un obxecto sobre o cal se poden engadir responsabilidades.

  • Decorador (decorator)

Implementa a interface da superclase Compoñente delegando no compoñente asociado. Mantén unha referencia ao compoñente asociado.

  • Decorador Concreto (ConcreteDecorator)

Engade responsabilidades ao compoñente.

Colaboracións editar

  • O decorador redirixe as peticións ao compoñente asociado.
  • Opcionalmente, pode realizar tarefas antes ou despois de redirixir a petición.

Consecuencias editar

O uso do padrón ten, polo menos, dúas vantaxes:

  • Máis flexible que a herdanza estática.

Co uso de decoradores, pódense engadir e eliminar responsabilidades en tempo de execución. Ademais disto, o uso de herdanza require a creación dunha nova clase para cada responsabilidade adicional. Isto dá lugar a moitas clases, e pode facer necesario o uso de herdanza múltiple (situación que se evita co uso de decoradores).

  • Evita a aparición de clases con moitas responsabilidades nas clases superiores da xerarquía.

O decorador ofrece un enfoque para engadir responsabilidades só do que se necesita. En vez de intentar incorporar todas as funcionalidades posibles nunha clase, podemos definir unha clase simple e, a posteriori, ir engadindo funcionalidades de forma incremental con obxectos decorador. Isto trae consigo a vantaxe de non ter que "pagar" por funcións non requiridas, xa que só se engade o que se necesita. Pero tamén ten dous inconvenientes fundamentais:

  • Un decorador e o seu compoñente non son idénticos.

O decorador compórtase como un envoltorio transparente. Pero desde o punto de vista da identidade de obxectos, estes non son idénticos, polo tanto non deberíamos apoiarnos na identidade cando estamos usando decoradores.

  • Gran cantidade de obxectos pequenos.

O uso de decoradores dá como resultado sistemas formados por multitude de obxectos pequenos moi parecidos.

Implementación editar

 public abstract class Compoñente{
    abstract public void operacion();
 }

 public class CompoñenteConcreto extends Compoñente{
    public void operacion(){
        System.out.println("CompoñenteConcreto.operacion()");
    }
 }

 public abstract class Decorador extends Compoñente{
    private Compoñente _compoñente;
       
	public Decorador(Compoñente compoñente){
		_compoñente = compoñente;
	}
	
	public void operacion(){
		_compoñente.operacion();
	}
 }

 public class DecoradorConcretoA extends Decorador{
	private String _propiedadeEngadida;
	
	public DecoradorConcretoA(Compoñente compoñente){
        super(compoñente);
    }
    
	public void operacion(){
		super.operacion();
		_propiedadeEngadida = "Nova propiedade";
		System.out.println("DecoradorConcretoA.operacion()");
	}
 }

 public class DecoradorConcretoB extends Decorador{
    public DecoradorConcretoB(Compoñente compoñente){
        super(compoñente);
    }
    
	public void operacion(){
		super.operacion();
		comportamentoEngadido();
		System.out.println("DecoradorConcretoB.operacion()");
	}
	
	public void comportamentoEngadido(){
		System.out.println("Comportamiento B engadido");
	}
 }

 public class Cliente{
 	public static void main(String[] args){
 		CompoñenteConcreto c = new CompoñenteConcreto();
 		DecoradorConcretoA d1 = new DecoradorConcretoA(c);
 		DecoradorConcretoB d2 = new DecoradorConcretoB(d1);
 		d2.operacion();
 	}
 }

Diagrama de Secuencia editar

 
Diagrama de secuencia do código.

A saída do programa sería a seguinte:

  • CompoñenteConcreto.operacion()
  • DecoradorConcretoA.operacion()
  • Comportamiento B engadido
  • DecoradorConcretoB.operacion()

Bibliografía editar

E. Gamma, R. Helm, R. Johnson and J. Vlissides (1994). Design Patterns: elements of reusable object-oriented software. Addison-Wesley.