Design patterns
Patrón decorador
Buscar..
Introducción
El patrón Decorator permite que un usuario agregue una nueva funcionalidad a un objeto existente sin alterar su estructura. Este tipo de patrón de diseño se encuentra bajo un patrón estructural ya que este patrón actúa como un envoltorio para la clase existente.
Este patrón crea una clase decoradora que envuelve la clase original y proporciona funcionalidad adicional manteniendo intacta la firma de los métodos de clase.
Parámetros
Parámetro | Descripción |
---|---|
Bebida | puede ser té o café |
VendingMachineDecorator
Definición de decorador según Wikiepdia:
El patrón Decorator se puede usar para extender (decorar) la funcionalidad de un determinado objeto de forma estática, o en algunos casos en tiempo de ejecución, independientemente de otras instancias de la misma clase, siempre que se realicen algunos trabajos básicos en el momento del diseño.
El decorador asigna responsabilidades adicionales a un objeto dinámicamente. Los decoradores ofrecen una alternativa flexible a las subclases para extender la funcionalidad.
Patrón decorador contiene cuatro componentes.
- Interfaz de componentes: define una interfaz para ejecutar operaciones particulares
- ConcreteComponent: implementa las operaciones definidas en la interfaz de componentes
- Decorador (abstracto): es una clase abstracta, que amplía la interfaz del componente. Contiene interfaz de componentes. En ausencia de esta clase, necesita muchas subclases de ConcreteDecorators para diferentes combinaciones. La composición del componente reduce las subclases innecesarias.
- ConcreteDecorator: Posee la implementación de Abstract Decorator.
Volviendo al código de ejemplo,
- La bebida es componente. Define un método abstracto: decorateBeverage
- El té y el café son implementaciones concretas de bebidas .
- BeverageDecorator es una clase abstracta, que contiene Beverage
- SugarDecorator y LemonDecorator son decoradores de concreto para BeverageDecorator.
EDITAR: Cambié el ejemplo para reflejar el escenario del mundo real de calcular el precio de la bebida agregando uno o más sabores como el azúcar, el limón, etc. (los sabores son decoradores)
abstract class Beverage {
protected String name;
protected int price;
public Beverage(){
}
public Beverage(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
protected void setPrice(int price){
this.price = price;
}
protected int getPrice(){
return price;
}
protected abstract void decorateBeverage();
}
class Tea extends Beverage{
public Tea(String name){
super(name);
setPrice(10);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
class Coffee extends Beverage{
public Coffee(String name){
super(name);
setPrice(15);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
abstract class BeverageDecorator extends Beverage {
protected Beverage beverage;
public BeverageDecorator(Beverage beverage){
this.beverage = beverage;
setName(beverage.getName()+"+"+getDecoratedName());
setPrice(beverage.getPrice()+getIncrementPrice());
}
public void decorateBeverage(){
beverage.decorateBeverage();
System.out.println("Cost of:"+getName()+":"+getPrice());
}
public abstract int getIncrementPrice();
public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
public SugarDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateSugar();
}
public void decorateSugar(){
System.out.println("Added Sugar to:"+beverage.getName());
}
public int getIncrementPrice(){
return 5;
}
public String getDecoratedName(){
return "Sugar";
}
}
class LemonDecorator extends BeverageDecorator{
public LemonDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateLemon();
}
public void decorateLemon(){
System.out.println("Added Lemon to:"+beverage.getName());
}
public int getIncrementPrice(){
return 3;
}
public String getDecoratedName(){
return "Lemon";
}
}
public class VendingMachineDecorator {
public static void main(String args[]){
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
}
}
salida:
Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon
Este ejemplo calcula el costo de la bebida en la máquina expendedora después de agregar muchos sabores a la bebida.
En el ejemplo anterior:
Costo del té = 10, Limón = 3 y Azúcar = 5. Si haces Azúcar + Limón + Té, cuesta 18.
Costo del café = 15, Limón = 3 y Azúcar = 5. Si haces Azúcar + Limón + Café, cuesta 23
Al utilizar el mismo Decorador para ambas bebidas (té y café), se ha reducido el número de subclases. En ausencia del patrón de Decorator, deberías tener diferentes subclases para diferentes combinaciones.
Las combinaciones serán así:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
etc.
Al utilizar el mismo Decorator
para ambas bebidas, el número de subclases se ha reducido. Es posible debido a la composition
lugar del concepto de inheritance
utilizado en este patrón.
Comparación con otros patrones de diseño (del artículo de fuente )
Adaptador proporciona una interfaz diferente a su tema. Proxy proporciona la misma interfaz. Decorador proporciona una interfaz mejorada.
El adaptador cambia la interfaz de un objeto, Decorator mejora las responsabilidades de un objeto.
Composite y Decorator tienen diagramas de estructura similares, lo que refleja el hecho de que ambos dependen de la composición recursiva para organizar un número abierto de objetos
Decorator está diseñado para permitirle agregar responsabilidades a los objetos sin crear subclases. El enfoque del compuesto no es el embellecimiento sino la representación.
Decorador y Proxy tienen diferentes propósitos pero estructuras similares.
Decorador te permite cambiar la piel de un objeto. La estrategia te permite cambiar las agallas.
Casos de uso clave:
- Añadir funcionalidades / responsabilidades adicionales dinámicamente
- Eliminar funcionalidades / responsabilidades dinámicamente.
- Evite demasiada subclasificación para agregar responsabilidades adicionales.
Decorador de caching
Este ejemplo muestra cómo agregar capacidades de almacenamiento en caché a DbProductRepository
usando el patrón Decorator. Este enfoque se adhiere a los principios de SOLID porque le permite agregar almacenamiento en caché sin violar el principio de responsabilidad única o el principio de apertura / cierre .
public interface IProductRepository
{
Product GetProduct(int id);
}
public class DbProductRepository : IProductRepository
{
public Product GetProduct(int id)
{
//return Product retrieved from DB
}
}
public class ProductRepositoryCachingDecorator : IProductRepository
{
private readonly IProductRepository _decoratedRepository;
private readonly ICache _cache;
private const int ExpirationInHours = 1;
public ProductRepositoryCachingDecorator(IProductRepository decoratedRepository, ICache cache)
{
_decoratedRepository = decoratedRepository;
_cache = cache;
}
public Product GetProduct(int id)
{
var cacheKey = GetKey(id);
var product = _cache.Get<Product>(cacheKey);
if (product == null)
{
product = _decoratedRepository.GetProduct(id);
_cache.Set(cacheKey, product, DateTimeOffset.Now.AddHours(ExpirationInHours));
}
return product;
}
private string GetKey(int id) => "Product:" + id.ToString();
}
public interface ICache
{
T Get<T>(string key);
void Set(string key, object value, DateTimeOffset expirationTime)
}
Uso:
var productRepository = new ProductRepositoryCachingDecorator(new DbProductRepository(), new Cache());
var product = productRepository.GetProduct(1);
El resultado de invocar GetProduct
será: recuperar el producto de la memoria caché (responsabilidad del decorador), si el producto no estaba en la memoria caché, continúe con la invocación a DbProductRepository
y recupere el producto de DB. Después de que este producto se pueda agregar al caché, las llamadas subsiguientes no afectarán a la base de datos.