1. 程式人生 > 實用技巧 >【設計模式(九)】結構型模式之裝飾器模式

【設計模式(九)】結構型模式之裝飾器模式

個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道

如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充


前言

中秋剛過沒多久,雖然我這種粗人對月餅無感,但是公司發的肯定得收的嘛

拿回家當零食吃算了,一個硬紙袋,開啟是一個盒子,盒子開啟時十多個小盒子,小盒子開啟是塑料包裝的月餅,撕開塑料包裝,終於能吃了

吃了一口,emmm就這?還沒小時候吃的冰糖五仁月餅好吃呢,弄這麼花裡胡哨

就一塊味道並不咋地的月餅,花裡胡哨包裝一層又一層,何必呢?還不如改善工藝做好吃點

說的再難聽點,這些精心包裝的月餅,跟普通袋裝的月餅,區別也就只是包裝吧(雖然大部分人送的是心意)

包裝對於月餅,就只是裝飾而已,差不多做法生產的月餅,經過一層又一層包裝,就能變成在店鋪裡形形色色的禮品月餅,本質上依然是月餅

而且理論上包裝可以無限層的套娃。。。。


我們早上去早餐店買煎餅,可以加雞蛋、生菜、肉鬆等等,從一個薄餅整成一個巨無霸,但說到底,仍然是個煎餅。


裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。

添加了新功能後是原物件的子類,根據里氏代換原則,新的物件也能夠被再次修飾,進而可以無限套娃


1.介紹

使用目的:向一個現有的物件新增新的功能,同時又不改變其結構

使用時機:在不想改變結構的情況下擴充套件類

解決問題:避免擴充套件時常規繼承方法,隨著擴充套件功能的增多,子類會很膨脹

實現方法Component 類充當抽象角色,修飾類引用和繼承 Component 類,具體擴充套件類重寫父類方法,而修飾類也同樣可被擴充套件

應用例項:

  • 買手抓餅的時候,我們並非只有做之前可以說加什麼,做完之後加肉鬆,或者加火腿什麼的都是可以的,但怎麼加都是手抓餅
  • 化妝的時候,經常是塗塗抹抹好幾層,而且順序並不嚴格固定,也可以最後根據情況再塗幾層,怎麼好看怎麼來,但怎麼塗都是自己的臉

優點

  1. 裝飾類和被裝飾類可以獨立發展,不會相互耦合
  2. 可以被多次裝飾,使用時自由度高

缺點:多層裝飾時思路比較複雜,需要清晰的理解業務


2.結構

通常包括4個角色

  • 抽象元件角色(Component)
    : 定義可以動態新增任務的物件的介面
  • 具體元件角色(ConcreteComponent):定義一個要被裝飾器裝飾的物件,即 Component 的具體實現
  • 抽象裝飾器(Decorator): 維護對元件物件和其子類元件的引用,需要實現 Component
  • 具體裝飾器角色(ConcreteDecorator):向元件新增新的職責

  • ConcreteComponent負責Component的具體實現
  • Decorator是一個虛擬類,需要實現Component,並持有一個Component物件
  • ConcreteDecorator作為裝飾器,負責給元件新增職責

ConcreteDecorator作為Decorator的子類,根據里氏代換原則,也可被ConcreteDecorator持有並裝飾

說白了就是,我裝飾我自己


3.實現

  1. 定義抽象元件角色(Component)

    interface Component {
        String operate();
    }
    
  2. 定義具體元件角色(ConcreteComponent),並實現Component

    class ConcreteComponent implements Component {
    
        @Override
        public String operate() {
            return "原始物件";
        }
    }
    
  3. 定義抽象裝飾器(Decorator),實現Component

    abstract class Decorator implements Component {
        private Component component;
    
        public Decorator(Component component) {
            this.component = component;
        }
    
        @Override
        public String operate() {
            //呼叫被裝飾者的方法
            return component.operate();
        }
    }
    
  4. 定義具體裝飾器角色(ConcreteDecorator),繼承Component

    class ConcreteDecoratorA extends Decorator {
    
        public ConcreteDecoratorA(Component component) {
            super(component);
        }
    
        @Override
        public String operate() {
            return super.operate() + " 加上修飾器A";
        }
    }
    
    class ConcreteDecoratorB extends Decorator {
    
        public ConcreteDecoratorB(Component component) {
            super(component);
        }
    
        @Override
        public String operate() {
            return super.operate() + " 加上修飾器B";
        }
    }
    
  5. 測試客戶端

    public class DecoratorTest {
        public static void main(String[] args) {
            Component component = new ConcreteComponent();
            Component componentA=new ConcreteDecoratorA(component);
            Component componentAB=new ConcreteDecoratorB(componentA);
            Component componentABA=new ConcreteDecoratorA(componentAB);
    
            Component componentBAB=new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent())));
    
            System.out.println(componentABA.operate());
            System.out.println(componentBAB.operate());
    
        }
    

完整程式碼

package com.company.test.decorator;

interface Component {
    String operate();
}

class ConcreteComponent implements Component {

    @Override
    public String operate() {
        return "原始物件";
    }
}

abstract class Decorator implements Component {
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public String operate() {
        //呼叫被裝飾者的方法
        return component.operate();
    }
}

class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public String operate() {
        return super.operate() + " 加上修飾器A";
    }
}

class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public String operate() {
        return super.operate() + " 加上修飾器B";
    }
}

public class DecoratorTest {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        Component componentA=new ConcreteDecoratorA(component);
        Component componentAB=new ConcreteDecoratorB(componentA);
        Component componentABA=new ConcreteDecoratorA(componentAB);

        Component componentBAB=new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent())));

        System.out.println(componentABA.operate());
        System.out.println(componentBAB.operate());

    }
}

執行結果


4.小結

4.1.為何要使用裝飾模式

  1. 解耦ConcreteComponentConcreteDecorator,從常見的繼承關係變為關聯關係,使他們可以獨立變化,只需要遵守共同的規則(Component)即可

    否則只能使他們一層一層繼承,耦合度極高,不利於擴充套件

  2. ConcreteComponentConcreteDecorator都算是Component的實現,因此對於Component適用的介面,ConcreteComponentConcreteDecorator都適用,這樣以來,客戶端使用和外部介面對接也會簡單很多

    因此外部程式碼只需要針對Component設計即可

  3. 可以自己裝飾自己,是獨一無二的優點,可以無限制的自由增加功能

4.2.存在的隱患

均為設計或者管理不當導致,也就是人為錯誤,可以憑藉開發者能力規避的東西

  1. 套娃次數太多,會導致邏輯混亂

    比如server層之間的互相引用,很容易出現套娃,邏輯極其混亂

  2. 死迴圈

    類似於遞迴的死迴圈爆棧

  3. 裝載順序報錯(先有雞還是先有蛋?)

    比如兩個service(A和B)互相引用,那麼裝載A時需要先裝載B,裝載B時需要先裝載A,會直接報錯


後記

將相似的目標提取其共同點,從而可以進行部分一致性操作,而目標本身只需要關注自己的特點,將共同點交由介面或者父類處理

其實這也是多型和繼承的目的,所以從學習Java開始,我們其實就在按照這種思想設計程式,組合模式不過是其中一種方案而已


作者:Echo_Ye

WX:Echo_YeZ

Email :[email protected]

個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)