Decorator

En la programació orientada a objectes, el patró Decorator (alternativament conegut com a Wrapper, un nom amb què també és conegut el patró Adaptador) és un patró de disseny que permet afegir un comportament a un objecte, tant estàticament com dinàmicament, sense que la resta d'objectes de la mateixa classe vegin alterat el seu comportament [1] El patró Decorator és útil sovint per complir el principi de responsabilitat única, car permet dividir una funcionalitat en diverses classes amb una única àrea d'afectació.[2]

Propòsit

[modifica]
Diagrama de classes UML del patró Decorator

El patró Decorator s'utilitza per estendre (decorar) la funcionalitat d'un cert objecte de forma estàtica, o en alguns casos en temps d'execució, independentment de la resta d'instàncies de la mateixa classe, en el cas que hagi estat dissenyat prèviament. Aquest comportament s'aconsegueix dissenyant una classe decorator nova que encapsuli la classe original. Aquest encapsulament es podria aconseguir seguint els següents passos:

  1. Afegir una subclasse de la classe "Component" original en una "classe" Decorator (com es pot observar en l'esquema UML).
  2. Afegir un punter a Component en forma de camp en la classe de Decorator.
  3. Passar per paràmetres un Component al constructor Decorator per inicialitzar el punter de Component.
  4. En la classe Decorator, redireccionar tots els mètodes de "Component" al punter "Component".
  5. Sobreescriure qualsevol mètode de Component que es necessiti modificar el seu comportament en la classe ConcreteDecorator.

Aquest patró està dissenyat de manera que múltiples decoradors poden ser apilats un sobre l'altre, cada cop afegint-hi una nova funcionalitat al(s) mètode(s) sobreescrit(s).

S'entén que els decoradors i l'objecte de la classe original compartiran un conjunt de característiques comunes. En l'esquema anterior, el mètode "operation()" està disponible tant en la versió decorada com en la no decorada.

Les característiques del Decorator (per exemple, mètodes, propietats, o d'altres membres) estan definides normalment per un interfície, per mixin o per l'herència de classes que és compartida pels decorators i l'objecte decorat. En l'exemple anterior, la classe "Component" és heretada tant per "ConcreteComponent" com per la subclasse que descendeix de "Decorador".

El patró Decorator és una alternativa a l'herència. L'herència afegeix comportament en temps de compilació i el canvi afecta a totes les instàncies de la classe original; fet que difereix del Decorator que proporciona un nou comportament en temps d'execució en objectes concrets.

Exemple

[modifica]

El següent exemple Java ambientat en la preparació de cafè il·lustra l'ús de decoradors. En aquest exemple només es tenen en compte costos i ingredients.

// La classe abstracta Cafe defineix la funcionalitat de Cafe implementada pel decorador
public abstract class Cafe {
 public abstract double getCost(); // Retorna el cost del cafè
 public abstract String getIngredients(); // Retorna els ingredients del cafè
}

// Extensió d'un simple "Cafe" sense cap ingredient extra
public class CafeSimple extends Cafe {
 public double getCost() {
 return 1;
 }

 public String getIngredients() {
 return "Cafè";
 }
}

La següent classe conté els decoradors per totes les classes Cafè, heretant les classes decorador pròpies.

// Classe abstracte Decorator - fixi-se que estén de la classe abstracta Cafe
public abstract class CafeDecorator extends Cafe {
 protected final Cafe CafeDecorator;

 public CafeDecorator(Cafe c) {
 this.cafeDecorat = c;
 }

 public double getCost() { // Implementació dels mètodes de la classe abstracta
 return cafeDecorat.getCost();
 }

 public String getIngredients() {
 return cafeDecorat.getIngredients();
 }
}

// Decorator AmbLlet barreja llet amb cafè.
// Fixi's que estén de CafeDecorator.
class AmbLlet extends CafeDecorator {
 public AmbLlet(Cafe c) {
 super(c);
 }

 public double getCost() { // Substitució de mètode definit en la [[superclasse]] abstracta
 return super.getCost() + 0.5;
 }

 public String getIngredients() {
 return super.getIngredients() + ", Llet";
 }
}

// Decorator AmbEncenallsXocolata barreja encenalls de xocolata amb el cafè.
// Fixi-se que estén CafeDecorator.
class AmbEncenallsXocolata extends CafeDecorator {
 public AmbEncenallsXocolata(Cafe c) {
 super(c);
 }

 public double getCost() {
 return super.getCost() + 0.2;
 }

 public String getIngredients() {
 return super.getIngredients() + ", Encenalls de xocolata";
 }
}

El següent tros de codi és el programa que crea una instància Cafe que està totalment decorada (amb llet i encenalls de xocolata), i calcula el cost del cafè i mostra els seus ingredients:

public class Main {
 public static void printInfo(Cafe c) {
 System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 }

 public static void main(String[] args) {
 Cafe c = new CafeSimple();
 printInfo(c);

 c = new AmbLlet(c);
 printInfo(c);

 c = new AmbEncenallsXocolata(c);
 printInfo(c);
 }
}

The output of this program is given below:

Cost: 1.0; Ingredients: Cafè
Cost: 1.5; Ingredients: Cafè, Llet
Cost: 1.7; Ingredients: Cafè, Llet, Encenalls de xocolata

Referències

[modifica]
  1. Gamma, Erich; etal. Design Patterns. Reading, MA: Addison-Wesley Publishing Co, Inc., 1995, p. 175ff. ISBN 0-201-63361-2. 
  2. «How to Implement a Decorator Pattern». Arxivat de l'original el 2015-07-07. [Consulta: 7 juliol 2015].