Design patterns
デコレータパターン
サーチ…
前書き
デコレータパターンを使用すると、構造を変更することなく、既存のオブジェクトに新しい機能を追加することができます。このタイプのデザインパターンは、このパターンが既存のクラスへのラッパーとして機能するため、構造パターンの下にあります。
このパターンは、元のクラスをラップするデコレータクラスを作成し、クラスメソッドのシグネチャをそのまま維持する追加の機能を提供します。
パラメーター
パラメータ | 説明 |
---|---|
飲料 | それは紅茶またはコーヒーにすることができます |
VendingMachineDecorator
Wikiepdiaによるデコレータの定義:
Decoratorパターンは、設計時にいくつかの基礎作業が行われた場合、特定のオブジェクトの機能を静的に、または同じクラスの他のインスタンスとは独立して、実行時に拡張(装飾)するために使用できます。
デコレータは、オブジェクトに動的に追加の責任を付与します。デコレータは、機能を拡張するためにサブクラス化する柔軟な代替手段を提供します。
デコレータパターンには4つのコンポーネントがあります。
- コンポーネントインタフェース:特定の操作を実行するインタフェースを定義します。
- ConcreteComponent:コンポーネントインタフェースで定義された操作を実装します。
- Decorator(Abstract):コンポーネントインタフェースを拡張する抽象クラスです。それはコンポーネントインタフェースを含んでいます。このクラスが存在しない場合、さまざまな組み合わせのConcreteDecoratorsの多くのサブクラスが必要です。コンポーネントの構成は不要なサブクラスを削減します。
- ConcreteDecorator:Abstract Decoratorの実装を保持します。
サンプルコードに戻ると、
- 飲料は成分である。これは、抽象メソッドを定義します:decorateBeverage
- 紅茶とコーヒーは、 飲料の具体的な実装です。
- BeverageDecoratorは抽象クラスで、 Beverageを含んでいます
- SugarDecoratorとLemonDecoratorはBeverageDecoratorのコンクリートデコレータです。
編集:シュガー、レモンなどの1つまたは複数の味を加えることによって飲料の価格を計算する現実のシナリオを反映する例を変更しました(味はデコレータです)
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();
}
}
出力:
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
この例では、飲料に多くの味を加えた後、自動販売機で飲料のコストを計算します。
上記の例では:
紅茶= 10、レモン= 3、砂糖= 5です。シュガー+レモン+紅茶を作る場合は18です。
コーヒーのコスト= 15、レモン= 3、シュガー= 5です。シュガー+レモン+コーヒーを作る場合は23
両方の飲料(Tea and Coffee)に同じDecoratorを使用することにより、サブクラスの数が減少しました。 Decoratorパターンがない場合は、さまざまな組み合わせに対して異なるサブクラスを用意する必要があります。
組み合わせは次のようになります:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
等
両方の飲料に同じDecorator
を使用することにより、サブクラスの数が減少しました。このパターンで使用されるinheritance
コンセプトではなく、 composition
起因する可能性があります。
他のデザインパターンとの比較( ソース作成記事から)
アダプターは、その主体と異なるインターフェースを提供します。 プロキシは同じインターフェイスを提供します。 Decoratorは、拡張されたインタフェースを提供します。
アダプタは 、オブジェクトのインターフェイスを変更し、 デコレータは、オブジェクトの責任を強化します。
コンポジットとデコレータの構造図は似ていますが、どちらも再帰的なコンポジションに依存してオープンエンドのオブジェクトを構成している
Decoratorは、サブクラス化せずにオブジェクトに責任を追加できるように設計されています。 コンポジットの焦点は装飾ではなく、表現
デコレータとプロキシは異なる目的を持っていますが、類似の構造
デコレータでは、オブジェクトのスキンを変更できます。 戦略はあなたが勇気を変えることを可能にする。
主な使用例:
- 追加の機能/責任を動的に追加する
- 機能/責任を動的に削除する
- 追加の責任を追加するには、あまりにも多くのサブクラスを避けてください。
キャッシングデコレータ
この例では、Decoratorパターンを使用してDbProductRepository
キャッシング機能を追加する方法を示します。このアプローチは、 単一責任原理またはオープン/クローズド原則に違反することなくキャッシングを追加できるため、 SOLID原則に準拠しています。
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)
}
使用法:
var productRepository = new ProductRepositoryCachingDecorator(new DbProductRepository(), new Cache());
var product = productRepository.GetProduct(1);
GetProduct
の呼び出しの結果は次のようになります。キャッシュから製品を取得する(デコレータの責任)、キャッシュ内に製品が存在しない場合は、 DbProductRepository
への呼び出しが進み、DBから製品を取得します。この製品をキャッシュに追加すると、以降の呼び出しでDBにヒットしません。