[設計モード-🧬 構造]🎀 Decorator Pattern


🧬 構造モード


🎀 Decorator


デコレーションパターン(Decorator Pattern)は、特定の状況や用途に応じて、あるオブジェクトにデコレーション(機能)を付加するパターンである。


オブジェクトに追加機能を動的に追加し、拡張機能が必要な場合にサブクラスを代替する柔軟な代替案として使用します。


デコの構造パターンはカフェを例にとると分かりやすいです.
喫茶店で飲み物の注文を受けるプログラムをオブジェクト向けのプログラミング観点に設定したとします.
このプログラムでカフェの飲み物をデザインすれば、飲み物が共通に持つ性質を分けて「飲み物」のレベルにし、継承して使用します.

そこで,上記最高級のBeverage類を作成し,コーヒーモカ,バニラカフェラテ,アメニティカフェラテの4種類の飲料類を継承し,Beverage類を継承した.
しかし、実際のカフェでは、モカカフェでカップを追加したり、薄いクリームを追加したり、アメリカンカフェでカップを追加したりすることができます.それらの飲料さえクラス別に生成されると、以下に示すように非常に複雑になります.

複雑に見えますが...そうなると、一つのメニューを新しく開発しても、スナップショットの追加、カットの追加、javaチップの追加など、数十の新しい派生クラスが追加され、メンテナンスが非常に困難になります.
では、さまざまなオプションをBeverageクラスのBooleanタイプに管理するために、いくつかの改良を行います.

Beverageクラスのコストメソッドでは、if文を使用してhasMilk、hasCreamなどをチェックし、異なるオプションの価格を追加できます.
public int cost() {    
        int total = 0;
        if(hasMilk()) total+=500;
        if(hasShot()) total+=400;
        if(hasCream()) total+=300;
        if(hasJavachip()) total += 700;
        
        return total;
}
また,Beverageクラス継承のメニューは以下のように価格を決定することができる.
public class Americano extends Beverage {

    @Override
    public int cost() {
        return 5000 + super.cost();
    }
    
}
上記の例は一見適切に設計されているように見えるかもしれません.
しかし、二つの大きな問題もある.

  • 新しいオプションを追加または変更するたびに、スーパーレベルのBeverageを変更します.スーパークラスの頻繁な変更は決して良いデザインではありません.

  • すべてのオプションをBeverageで管理すると、サブクラスから不要な情報がすべて継承されます.グレープフルーツゼリー類がBeverageを継承し、スナップショットやジッタを追加する不要なオプションも管理している場合は、良い設計ではありません.
  • またhasMilkはbooleanなので、2回以上のオプションを追加することはできません.
    だから、total+=牛乳500+shot 500+...;このように使うためにはint型に変える必要があります.
    従って、上記設計は、対象設計の5大原則SOLIDにおいて、特に第2のOPCの設計に完全に違反する.
    OCP(Open Closed Principle):クラスは拡張に対してオープンであり、変更に対してクローズである.
    つまり、新しい機能を追加しようとすると、既存のスーパークラスを変更するのではなく、拡張することで簡単に追加できます!
    そこで、アクセサリーモードを適用してプログラムを再設計したいと思います.

    装飾図案の一般的な形態は以下の通りである.

    また、アプリケーションのデコレーションパターンのデザインは以下の通りです.
    装飾にはトップクラスのCondimentDecoratorクラスがあり、その他のすべてのオプションがCondimentDecoratorクラスを継承します.
    また、各メニュークラスとContimentDecoratorクラスは、Beverageクラスを継承しています.
    public abstract class Beverage {
    
        // 음료 이름
        String description = "Menu Name (No title)";
        // 음료 가격
        public abstract int cost();
        // 옵션들
        public String getDescription() {
            return description;
        }
        
    }
    上はトップクラスのBeverageクラスです.
    喫茶店で販売されているすべての飲み物はこの種類を継承しなければならない.コストは抽象的な方法であるため、サブクラスではそれぞれの価格でこの方法を超過して実施しなければならない.
    したがって,Beverageはもちろん抽象クラスとなる.
    public abstract class CondimentDecorator extends Beverage {
        public abstract String getDescription();
    }
    上は、Beverageクラスを継承するContimentDecoratorクラスです.
    装飾のトップクラスとして、その他のオプションはContimentDecoratorクラスを継承します.
    CondimentDecoratorクラスは、BeverageクラスのgetDescription()メソッドを抽象メソッドに簡略化します.
    CondimentDecoratorのオプションを継承するには、この点を実現する必要があります.
    したがってCondimentDecoratorも抽象クラスとなり,抽象クラスであるため,ここでBeverageクラスの抽象メソッドCost()を実現する必要はない.
    最上位レベルの中間クラスを継承する
    抽象メソッドを使用して、最上位レベルのメソッドを上書きできます.
    中間層から最上層にない義務性を下に与えることもできる.
    public class Americano extends Beverage {
     
        public Americano() {
        	super();
            description = "아메리카노";
        }
     
        @Override
        public int cost() {
            return 3800;
        }
     
    }
    public class CaffeMocha extends Beverage {
     
        public CaffeMocha() {
            super();
            description = "카페모카";
        }
     
        @Override
        public int cost() {
            return 4800;
        }
     
    }
    BeverageクラスのAmericanoとCaffeMochaクラスを継承します.
    生成者で飲料の名称を指定し,抽象的な方法のコストメソッドを重ね,それぞれの飲料に基づいて価格を決定する.
    public class Whip extends CondimentDecorator {
     
        Beverage beverage;
        
        public Whip(Beverage beverage) {
            super();
            this.beverage = beverage;
        }
     
        @Override
        public String getDescription() {
            return beverage.getDescription() + ", 휘핑크림";
        }
     
        @Override
        public int cost() {
            return beverage.cost() + 500;
        }
        
    }
    public class Shot extends CondimentDecorator {
        
        Beverage beverage;
     
        public Shot(Beverage beverage) {
            super();
            this.beverage = beverage;
        }
     
        @Override
        public String getDescription() {
            return beverage.getDescription() + ", 샷";
        }
     
        @Override
        public int cost() {
            return beverage.cost() + 500;
        }
        
    }
    
    ContimentDecoratorクラスが継承するWhipクラスとShotクラス.
    抽象メソッドは、作成者から渡されたBeverageオブジェクトインスタンス(米国no、CaffeMochaなどの飲料)のメンバーとメソッドにアクセスすることによって上書きされる.
    CondimentDecoratorクラスも抽象クラスなので、コスト抽象メソッドは実現できません.追加のgetDescriptionメソッドは抽象メソッドとして抽象化されているので、WhipやShotなどの他のオプションクライアントで2つの抽象メソッドを実現する必要があります.
    次に、お客様クラスから対応するアクセサリーモードで設計された飲料クラスを注文する方法を見てみましょう.
    public class Customer {
     
        public static void main(String[] args) {
            
            Beverage MyCoffee = new CaffeMocha();
            //추가시킬 옵션의 매개변수로 자신의 인스턴스를 전달
            MyCoffee = new Shot(MyCoffee);
            MyCoffee = new Shot(MyCoffee);
            MyCoffee = new Cream(MyCoffee);
            MyCoffee = new Whip(MyCoffee);
            MyCoffee = new JavaChip(MyCoffee);
            
            System.out.println("메뉴 : " + beverage.getDescription());
            System.out.println("가격 : " + beverage.cost());
            
        }
    }
    Customer Classで2杯、クリーム、薄いクリーム、ジャワポテトの悪魔コーヒーモカを注文.
    すべてのオプションが500元ずつ増加するように設計されている場合、実行結果は次のとおりです.
    메뉴 : 카메모카, 샷, 샷, 크림, 휘핑크림, 자바칩
    가격 : 7300
    そう言うはずだ.
    コードでご覧のように、オプションを追加するときに自分のインスタンスを再渡すことで、JavaChip-Whip-CReam-shot-CaffeMochaで囲まれたオブジェクトを作成できます.
    このような形状のチェーンを作成するには、飲料の各メンバーを次のオプションに転送し続けます.
    このとき、最外部のJavaChipオブジェクトのgetDescriptionメソッドを実行すると、飲料に格納されたWhipオブジェクトのgetDescriptionが、CreamオブジェクトのgetDescriptionメソッドを順次呼び出し、最終的にCaffeMochaオブジェクトに到達し、CaffeMochaオブジェクトのgetDescriptionメソッドが呼び出される.
    その後、CaffeMochaオブジェクトのdescriptionから車に戻り、2つのShotオブジェクトとCreamオブジェクトを順に返し、文字列を追加し続け、最終的に「カメラ、スナップショット、スナップショット、クリーム、薄いクリーム、Javaチップ」文字列を完了します.
    costメソッドと同様に呼び出しはjavaチップ->モカから徐々に価格を増やしていきます.
    現在のように、カフェの飲み物にオプションを追加して価格を上げる一方、getDescriptionに文字列を追加する説明は、アクセサリーモードを理解しやすい.
    実際には追加の機能が追加されています.
    代表的なのは、Decoratorモードを使用しているJava I/Oクラスです.
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    br.readLine();
    解いてみれば.
    BufferedReader br = new InputStreamReader(System.in);
    br = new BufferedReader(br);
    br.readLine();
    このような装飾パターンが適用されることがわかる.