Design patterns
Wzór dekoratora
Szukaj…
Wprowadzenie
Wzór dekoratora pozwala użytkownikowi dodać nową funkcjonalność do istniejącego obiektu bez zmiany jego struktury. Ten typ wzoru projektowego jest objęty wzorem strukturalnym, ponieważ wzór ten stanowi opakowanie dla istniejącej klasy.
Ten wzorzec tworzy klasę dekoratora, która otacza klasę oryginalną i zapewnia dodatkową funkcjonalność, utrzymując nienaruszoną sygnaturę metod klasy.
Parametry
Parametr | Opis |
---|---|
Napój | może to być herbata lub kawa |
VendingMachineDecorator
Definicja dekoratora według Wikiepdii:
Wzorzec Dekoratora można wykorzystać do rozszerzenia (udekorowania) funkcjonalności określonego obiektu statycznie lub w niektórych przypadkach w czasie wykonywania, niezależnie od innych wystąpień tej samej klasy, pod warunkiem, że pewne prace przygotowawcze zostaną wykonane w czasie projektowania.
Dekorator dynamicznie przypisuje dodatkowe obowiązki do obiektu. Dekoratorzy stanowią elastyczną alternatywę dla podklas w celu rozszerzenia funkcjonalności.
Wzór dekoratora zawiera cztery elementy.
- Interfejs komponentu: Definiuje interfejs do wykonywania określonych operacji
- ConcreteComponent: Implementuje operacje zdefiniowane w interfejsie Component
- Dekorator (streszczenie): jest to klasa abstrakcyjna, która rozszerza interfejs komponentu. Zawiera interfejs Component. W przypadku braku tej klasy potrzebujesz wielu podklas betonowych dekoratorów dla różnych kombinacji. Skład składnika zmniejsza niepotrzebne podklasy.
- ConcreteDecorator: zawiera implementację programu Abstract Decorator.
Wracając do przykładowego kodu,
- Napój jest składnikiem. Definiuje metodę abstrakcyjną: decorateBeverage
- Herbata i kawa to konkretne realizacje napojów .
- BeverageDecorator to klasa abstrakcyjna, która zawiera napoje
- SugarDecorator i LemonDecorator są betonowymi dekoratorami do napojów dekoracyjnych.
EDYCJA: Zmieniłem przykład, aby odzwierciedlić rzeczywisty scenariusz obliczania ceny napojów, dodając jeden lub więcej smaków, takich jak cukier, cytryna itp. (Smaki są dekoratorami)
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();
}
}
wynik:
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
Ten przykład oblicza koszt napoju w automacie po dodaniu wielu smaków do napoju.
W powyższym przykładzie:
Koszt herbaty = 10, cytryny = 3 i cukru = 5. Jeśli zrobisz cukier + cytrynę + herbatę, kosztuje 18.
Koszt kawy = 15, cytryny = 3 i cukru = 5. Jeśli zrobisz cukier + cytrynę + kawę, kosztuje 23
Dzięki zastosowaniu tego samego dekoratora dla obu napojów (herbata i kawa) liczba podklas została zmniejszona. W przypadku braku wzoru dekoratora powinieneś mieć różne podklasy dla różnych kombinacji.
Kombinacje będą wyglądać następująco:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
itp.
Dzięki zastosowaniu tego samego Decorator
dla obu napojów zmniejszono liczbę podklas. Jest to możliwe ze względu na composition
a nie koncepcję inheritance
zastosowaną w tym wzorze.
Porównanie z innymi wzorami projektowymi (z artykułu o zaopatrzeniu )
Adapter zapewnia inny interfejs dla swojego tematu. Serwer proxy zapewnia ten sam interfejs. Decorator zapewnia ulepszony interfejs.
Adapter zmienia interfejs obiektu, Dekorator zwiększa obowiązki obiektu.
Kompozyt i Dekorator mają podobne schematy struktur, odzwierciedlając fakt, że oba polegają na rekurencyjnej kompozycji do organizowania otwartej liczby obiektów
Program Decorator umożliwia dodawanie obowiązków do obiektów bez tworzenia podklas. Kompozyt koncentruje się nie na upiększeniu, ale na reprezentacji
Dekorator i serwer proxy mają różne cele, ale podobne struktury
Dekorator umożliwia zmianę karnacji obiektu. Strategia pozwala zmienić odwagę.
Kluczowe przypadki użycia:
- Dynamicznie dodawaj dodatkowe funkcje / obowiązki
- Dynamicznie usuwaj funkcje / obowiązki
- Unikaj zbyt wielu podklas, aby dodać dodatkowe obowiązki.
Dekorator buforowania
W tym przykładzie pokazano, jak dodać funkcje buforowania do DbProductRepository
przy użyciu wzorca Decorator. To podejście jest zgodne z zasadami SOLID, ponieważ pozwala dodawać buforowanie bez naruszania zasady pojedynczej odpowiedzialności lub zasady otwartej / zamkniętej .
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)
}
Stosowanie:
var productRepository = new ProductRepositoryCachingDecorator(new DbProductRepository(), new Cache());
var product = productRepository.GetProduct(1);
Wynikiem wywołania GetProduct
będzie: pobranie produktu z pamięci podręcznej (odpowiedzialność dekoratora), jeśli produkt nie był w pamięci podręcznej, kontynuuj wywoływanie do DbProductRepository
i pobierz produkt z DB. Po dodaniu tego produktu do pamięci podręcznej, aby kolejne połączenia nie trafiły do DB.