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.

introduzca la descripción de la imagen aquí

  1. Interfaz de componentes: define una interfaz para ejecutar operaciones particulares
  2. ConcreteComponent: implementa las operaciones definidas en la interfaz de componentes
  3. 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.
  4. ConcreteDecorator: Posee la implementación de Abstract Decorator.

Volviendo al código de ejemplo,

  1. La bebida es componente. Define un método abstracto: decorateBeverage
  2. El té y el café son implementaciones concretas de bebidas .
  3. BeverageDecorator es una clase abstracta, que contiene Beverage
  4. 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 )

  1. Adaptador proporciona una interfaz diferente a su tema. Proxy proporciona la misma interfaz. Decorador proporciona una interfaz mejorada.

  2. El adaptador cambia la interfaz de un objeto, Decorator mejora las responsabilidades de un objeto.

  3. 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

  4. 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.

  5. Decorador y Proxy tienen diferentes propósitos pero estructuras similares.

  6. Decorador te permite cambiar la piel de un objeto. La estrategia te permite cambiar las agallas.

Casos de uso clave:

  1. Añadir funcionalidades / responsabilidades adicionales dinámicamente
  2. Eliminar funcionalidades / responsabilidades dinámicamente.
  3. 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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow