Design patterns
Motivo decorativo
Ricerca…
introduzione
Il pattern Decorator consente all'utente di aggiungere nuove funzionalità a un oggetto esistente senza alterarne la struttura. Questo tipo di modello di progettazione rientra in un modello strutturale poiché questo modello funge da involucro per la classe esistente.
Questo modello crea una classe decoratore che racchiude la classe originale e fornisce funzionalità aggiuntive mantenendo intatta la firma dei metodi di classe.
Parametri
Parametro | Descrizione |
---|---|
bevanda | può essere tè o caffè |
VendingMachineDecorator
Definizione di Decoratore secondo Wikiepdia:
Il pattern Decorator può essere utilizzato per estendere (decorare) la funzionalità di un determinato oggetto in modo statico, o in alcuni casi in fase di esecuzione, indipendentemente da altre istanze della stessa classe, a condizione che alcune attività di base vengano eseguite in fase di progettazione.
Decoratore attribuisce responsabilità aggiuntive a un oggetto in modo dinamico. I decoratori offrono un'alternativa flessibile alla sottoclasse per estendere le funzionalità.
Il motivo decoratore contiene quattro componenti.
- Interfaccia componente: definisce un'interfaccia per eseguire operazioni particolari
- ConcreteComponent: implementa le operazioni definite nell'interfaccia Component
- Decoratore (Abstract): è una classe astratta, che estende l'interfaccia del componente. Contiene l'interfaccia Component. In assenza di questa classe, sono necessarie molte sottoclassi di ConcreteDecorators per combinazioni diverse. La composizione del componente riduce sottoclassi non necessarie.
- ConcreteDecorator: detiene l'implementazione di Abstract Decorator.
Tornando al codice di esempio,
- La bevanda è componente. Definisce un metodo astratto: decora la bevanda
- Tè e caffè sono implementazioni concrete della bevanda .
- BeverageDecorator è una classe astratta, che contiene Beverage
- SugarDecorator e LemonDecorator sono Decorators in calcestruzzo per BeverageDecorator.
EDIT: cambiato l'esempio per riflettere lo scenario del mondo reale di calcolare il prezzo della bevanda aggiungendo uno o più aromi come zucchero, limone ecc. (I sapori sono decoratori)
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();
}
}
produzione:
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
Questo esempio calcola il costo della bevanda nel distributore automatico dopo aver aggiunto molti aromi alla bevanda.
Nell'esempio sopra:
Costo del tè = 10, Limone = 3 e Zucchero = 5. Se produci Zucchero + Limone + Tè, costa 18.
Costo del caffè = 15, Limone = 3 e Zucchero = 5. Se produci Zucchero + Limone + Caffè, costa 23
Utilizzando lo stesso decoratore per entrambe le bevande (tè e caffè), il numero di sottoclassi è stato ridotto. In assenza del pattern Decorator, è necessario avere classi secondarie diverse per combinazioni diverse.
Le combinazioni saranno così:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
eccetera.
Usando lo stesso Decorator
per entrambe le bevande, il numero di sottoclassi è stato ridotto. È possibile a causa della composition
piuttosto che del concetto di inheritance
utilizzato in questo modello.
Confronto con altri modelli di design (dall'articolo di sourcemaking )
L'adattatore fornisce un'interfaccia diversa al suo soggetto. Proxy fornisce la stessa interfaccia. Decorator fornisce un'interfaccia migliorata.
Adapter modifica l'interfaccia di un oggetto, Decorator migliora le responsabilità di un oggetto.
Composito e Decoratore hanno diagrammi di struttura simili, il che riflette il fatto che entrambi si basano sulla composizione ricorsiva per organizzare un numero di oggetti aperti
Decorator è progettato per consentire di aggiungere responsabilità agli oggetti senza sottoclassi. L' attenzione del composito non è sull'abbellimento ma sulla rappresentazione
Decoratore e Proxy hanno scopi diversi ma strutture simili
Decoratore ti consente di cambiare la pelle di un oggetto. La strategia ti consente di cambiare il coraggio.
Casi d'uso principali:
- Aggiungi funzionalità / responsabilità aggiuntive in modo dinamico
- Rimuovere dinamicamente funzionalità / responsabilità
- Evita troppe sottoclasse per aggiungere ulteriori responsabilità.
Caching Decorator
Questo esempio mostra come aggiungere funzionalità di memorizzazione nella cache a DbProductRepository
utilizzando il pattern Decorator. Questo approccio è conforme ai principi SOLID perché consente di aggiungere il caching senza violare il principio di Responsabilità singola o principio Aperto / chiuso .
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);
Il risultato del GetProduct
di GetProduct
sarà: recuperare il prodotto dalla cache (responsabilità del decoratore), se il prodotto non era nella cache procedere con l'invocazione a DbProductRepository
e recuperare il prodotto dal DB. Dopo questo prodotto può essere aggiunto alla cache in modo che le chiamate successive non colpiranno il DB.