Design patterns
Dekorateur-Muster
Suche…
Einführung
Mit dem Dekorationsmuster kann ein Benutzer einem vorhandenen Objekt neue Funktionen hinzufügen, ohne seine Struktur zu ändern. Diese Art von Designmuster fällt unter Strukturmuster, da dieses Muster als Wrapper für eine vorhandene Klasse fungiert.
Dieses Muster erstellt eine Dekoratorklasse, die die ursprüngliche Klasse umschließt und zusätzliche Funktionalität bereitstellt, wobei die Signatur der Klassenmethoden erhalten bleibt.
Parameter
Parameter | Beschreibung |
---|---|
Getränk | Es kann Tee oder Kaffee sein |
VendingMachineDecorator
Definition von Decorator nach Wikiepdia:
Das Decorator-Muster kann verwendet werden, um die Funktionalität eines bestimmten Objekts statisch oder in einigen Fällen zur Laufzeit unabhängig von anderen Instanzen derselben Klasse zu erweitern (zu dekorieren), sofern zur Entwurfszeit einige Vorarbeiten ausgeführt werden.
Der Dekorateur fügt einem Objekt dynamisch zusätzliche Verantwortlichkeiten hinzu. Dekorateure bieten eine flexible Alternative zu Unterklassen zur Erweiterung der Funktionalität.
Dekorateur-Muster enthält vier Komponenten.
- Komponentenschnittstelle: Definiert eine Schnittstelle, um bestimmte Vorgänge auszuführen
- ConcreteComponent: Implementiert die in der Komponentenschnittstelle definierten Vorgänge
- Decorator (Abstract): Dies ist eine abstrakte Klasse, die die Komponentenschnittstelle erweitert. Es enthält eine Komponentenschnittstelle. In Abwesenheit dieser Klasse benötigen Sie viele Unterklassen von ConcreteDecorators für verschiedene Kombinationen. Die Zusammensetzung der Komponente reduziert unnötige Unterklassen.
- ConcreteDecorator: Es beinhaltet die Implementierung von Abstract Decorator.
Zurück zum Beispielcode,
- Getränk ist Bestandteil. Es definiert eine abstrakte Methode: decorateBeverage
- Tee und Kaffee sind konkrete Umsetzungen von Beverage .
- BeverageDecorator ist eine abstrakte Klasse, die Beverage enthält
- SugarDecorator und LemonDecorator sind konkrete Dekorateure für BeverageDecorator.
BEARBEITEN: Das Beispiel wurde geändert, um das reale Szenario der Berechnung des Getränkepreises durch Hinzufügen einer oder mehrerer Geschmacksrichtungen wie Zucker, Zitrone usw. zu berücksichtigen (Geschmacksrichtungen sind Dekorateure).
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();
}
}
Ausgabe:
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
In diesem Beispiel werden die Getränkekosten im Automaten berechnet, nachdem dem Getränk viele Geschmacksrichtungen hinzugefügt wurden.
In obigem Beispiel:
Teekosten = 10, Zitrone = 3 und Zucker = 5. Wenn Sie Zucker + Zitrone + Tee herstellen, kostet dies 18.
Kosten für Kaffee = 15, Zitrone = 3 und Zucker = 5. Wenn Sie Zucker + Zitrone + Kaffee herstellen, kostet dies 23
Durch die Verwendung des gleichen Decorators für beide Getränke (Tee und Kaffee) wurde die Anzahl der Unterklassen reduziert. In Abwesenheit des Decorator-Musters sollten Sie unterschiedliche Unterklassen für unterschiedliche Kombinationen haben.
Die Kombinationen werden so sein:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
usw.
Durch die Verwendung des gleichen Decorator
für beide Getränke wurde die Anzahl der Unterklassen reduziert. Dies ist aufgrund der composition
und nicht des in diesem Muster verwendeten inheritance
möglich.
Vergleich mit anderen Designmustern (aus dem Quelltextartikel )
Der Adapter bietet eine andere Schnittstelle zum Betreff. Proxy bietet dieselbe Schnittstelle. Decorator bietet eine erweiterte Benutzeroberfläche.
Der Adapter ändert die Oberfläche eines Objekts, Decorator verbessert die Verantwortlichkeiten eines Objekts.
Composite und Decorator weisen ähnliche Strukturdiagramme auf. Dies spiegelt die Tatsache wider , dass beide auf rekursive Komposition angewiesen sind, um eine unbegrenzte Anzahl von Objekten zu organisieren
Decorator ermöglicht das Hinzufügen von Verantwortlichkeiten zu Objekten ohne Unterklassen. Composite konzentriert sich nicht auf Verschönerung, sondern auf Repräsentation
Decorator und Proxy haben unterschiedliche Zwecke, aber ähnliche Strukturen
Mit Decorator können Sie die Skin eines Objekts ändern. Strategie lässt Sie den Mut ändern.
Wichtige Anwendungsfälle:
- Fügen Sie dynamisch weitere Funktionen / Verantwortlichkeiten hinzu
- Entfernen Sie Funktionalitäten / Verantwortlichkeiten dynamisch
- Vermeiden Sie zu viel Unterklassifizierung, um zusätzliche Verantwortlichkeiten hinzuzufügen.
Zwischenspeicher-Dekorateur
In diesem Beispiel wird DbProductRepository
, wie DbProductRepository
mithilfe des Decorator-Musters DbProductRepository
wird. Dieser Ansatz folgt den SOLID-Grundsätzen, da Sie das Zwischenspeichern hinzufügen können, ohne dass das Prinzip der Einzelverantwortung oder das Open / Closed-Prinzip verletzt wird.
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)
}
Verwendungszweck:
var productRepository = new ProductRepositoryCachingDecorator(new DbProductRepository(), new Cache());
var product = productRepository.GetProduct(1);
Das Ergebnis des Aufrufs von GetProduct
ist: Produkt aus dem Cache abrufen (Verantwortlichkeit des Dekorators), wenn sich das Produkt nicht im Cache befindet, fahren Sie mit dem Aufruf von DbProductRepository
und DbProductRepository
Produkt aus der DbProductRepository
ab. Nachdem dieses Produkt zum Cache hinzugefügt werden kann, trifft nachfolgende Aufrufe nicht die DB.