Decorator
También conocido como
Wrapper
Propósito
Adjunte responsabilidades adicionales a un objeto de forma dinámica. Los decoradores proporcionan una alternativa
flexible a la subclase para ampliar la funcionalidad.
Explicación
Ejemplo real
En las colinas cercanas vive un trol furioso. Normalmente va con las manos desnudas, pero a veces lleva un arma. Para
armar al troll no es necesario crear un nuevo troll sino decorarlo dinámicamente con un arma adecuada.
En pocas palabras
El patrón decorador permite cambiar dinámicamente el comportamiento de un objeto en tiempo de ejecución envolviéndolo
en un objeto de una clase decoradora.
Wikipedia says
En programación orientada a objetos, el patrón decorador es un patrón de diseño que permite añadir comportamiento a un
objeto individual, ya sea de forma estática o dinámica, sin afectar al comportamiento de otros objetos de la misma
clase. El patrón decorador suele ser útil para adherirse al Principio de Responsabilidad Única, ya que permite dividir
la funcionalidad entre clases con áreas de interés únicas, así como al Principio Abierto-Cerrado, al permitir extender
la funcionalidad de una clase sin modificarla.
Ejemplo programático
Tomemos el ejemplo del troll. En primer lugar tenemos un SimpleTroll
que implementa la interfaz Troll
:
public interface Troll {
void atacar();
int getPoderAtaque();
void huirBatalla();
}
@Slf4j
public class SimpleTroll implements Troll {
@Override
public void atacar() {
LOGGER.info("¡El troll intenta atraparte!");
}
@Override
public int getPoderAtaque() {
return 10;
}
@Override
public void huirBatalla() {
LOGGER.info("¡El troll chilla de horror y huye!");
}
}
A continuación, queremos añadir un palo para el troll. Podemos hacerlo de forma dinámica mediante el uso de un
decorador:
@Slf4j
public class TrollConGarrote implements Troll {
private final Troll decorado;
public TrollConGarrote(Troll decorado) {
this.decorado = decorado;
}
@Override
public void atacar() {
decorado.atacar();
LOGGER.info("¡El troll te golpea con un garrote!");
}
@Override
public int getPoderAtaque() {
return decorado.getPoderAtaque() + 10;
}
@Override
public void huirBatalla() {
decorado.huirBatalla();
}
}
Aquí está el troll en acción:
// troll simple
LOGGER.info("Un troll de aspecto simple se acerca.");
var troll = new SimpleTroll();
troll.atacar();
troll.huirBatalla();
LOGGER.info("Poder del troll simple: {}.\n", troll.getPoderAtaque());
// cambia el comportamiento del troll simple agregando un decorador
LOGGER.info("Un troll con un enorme garrote te sorprende.");
var trollConGarrote = new TrollConGarrote(troll);
trollConGarrote.atacar();
trollConGarrote.huirBatalla();
LOGGER.info("Poder del troll con garrote: {}.\n", trollConGarrote.getPoderAtaque());
Salida del programa:
Un troll de aspecto simple se acerca.
¡El troll intenta atraparte!
¡El troll chilla de horror y huye!
Poder del troll simple: 10.
Un troll con un enorme garrote te sorprende.
¡El troll intenta atraparte!
¡El troll te golpea con un garrote!
¡El troll chilla de horror y huye!
Poder del troll con garrote: 20.
Diagrama de clases
Aplicabilidad
Decorator se utiliza para:
- Añadir responsabilidades a objetos individuales de forma dinámica y transparente, es decir, sin
afectar a otros objetos. - Para responsabilidades que pueden ser retiradas.
- Cuando la extensión por subclase es poco práctica. A veces es posible un gran número de extensiones independientes
son posibles y producirían una explosión de subclases para soportar cada combinación. O la definición de una clase
puede estar oculta o no estar disponible para subclases.
Tutoriales
Usos conocidos
- java.io.InputStream, java.io.OutputStream,
java.io.Reader
y java.io.Writer - java.util.Collections#synchronizedXXX()
- java.util.Collections#unmodifiableXXX()
- java.util.Collections#checkedXXX()