1. 程式人生 > >裝飾模式(Decorate)-今天你要秀你的哪一面?

裝飾模式(Decorate)-今天你要秀你的哪一面?

@override isa 真的 廣告 基類 clas 增加 gravity int

裝飾模式可以動態的加入程序功能,避免因為過度子類化帶來的耦合,相比較用繼承方式的靜態,裝飾更為靈活.

意圖

動態地給一個對象增加一些額外的職責。就增加功能而言,Decorator模式比生成子類更為靈活。 --GOF

動態是指在程序運行時來決定,而靜態則是在編譯時就已經確定,例如使用組合的方式,可以動態決定功能,但如果使用繼承,則是在編譯時就要確定,這也就是在一些場景中推薦使用組合的原因.裝飾模式與我們上一篇所講的橋接模式,都是用了組合方式來解決因為多個變化維度導致的多繼承的問題.

解決的是什麽問題

裝飾模式是結構型模式,解決的問題是因為功能在多個維度上存在變化,為了避免多繼承而產生的,相對於多繼承實現多功能擴展,裝飾可以靈活的擴展,也可以讓子類職責清晰,不會順著一個方向變化膨脹.按照慣例,我們還是要舉例說明,同樣還是之前用過的紅警遊戲.

舉例說明

遊戲中有不同的坦克,比如天啟,光棱,幻影,灰熊等,我們在上一節中實現了坦克的繪制,開火,發動等基礎.現在需求來了,我們需要給這些坦克加入一些功能,例如衛星定位,炮彈消音,甚至挖掘功能,當然這裏的功能也許也現實情況不符合,目的只是為說明模式,需要說明的是,加入的是功能,這個功能是有一定重量的,比如隱身和變色這種應該不屬於功能一級,而是坦克自身的屬性,功能級別是如衛星定位,炮彈消音這種需要一系列工作組件來協作的.

初期分析

遊戲中的坦克類型不是固定的,所以為了適應以後的變化,抽象化一個Tank基類來實現,至於不同的坦克,天啟,光棱等都可以由不同的子類來實現,這樣在需要加入新的坦克類型時,只需要擴展方式增加子類既可,符合開閉原則


技術分享圖片
關系結構如圖所示,這是我們的主體類,現在要加入新的功能來豐富坦克,衛星定位,炮彈消音,挖掘,而且某一個坦克有什麽新功能是不一定的,也就是有可能天啟坦克會有衛星定位,也有可能會炮彈消音,也可能要變挖掘機了,也有可能是只基中的2種功能,或者三種功能的組合..光棱坦克也是一樣.

膨脹的子類,臃腫的繼承

順著之前的思路,要加入新的功能,我們考慮將三種功能抽象化為接口,分別為ISatellites(衛星定位),ISilencer(消音),IExcavate(挖掘),然後為天啟和光棱坦克加入接口實現.

技術分享圖片

如圖中關系所示,這裏象征性的將天啟坦克實現了消音功能,將光棱坦克實現了挖掘與定位功能.如果按照我們的需求,這將是一個排列組合,我們將要實現好多種坦克子類,大約有,消音的天啟,能定位的天啟,能挖掘的天啟.消音+定位的天啟,消音+定位+挖掘的天啟.....等,試問如果這樣實現下去,子類結構是不是膨脹了,如果面臨修改時將是一個惡夢,而且繼承結構也很復雜,顯的很臃腫,這也是一個典型的繼承濫用的例子,如果那樣我們的關系結構圖大概會變成這個樣子.
技術分享圖片

大量的實現子類的出現讓我們的關系結構雜亂不堪,圖中並未將所有排列都畫出.可想而知,如果再加一種坦克呢?

模式的引入

之所以有這樣的問題,其實就是有不同維度的功能變化,我們的主體Tank是順著坦克的不同類型來變化,而新加入的功能有著各種組合關系,還各種有著不同的實現內容,如果將這些變化混在一起,就變成了上面的樣子.當然並不是說繼承就一定有錯,只不過是在一些變化的點上需要用更為靈活的動態方式來實現,我們的例子中Tank與它的子類之間繼承就是穩固的,而另一條線擴展功能一系列接口(消音,定位..)則是不穩定的,也就是說一個坦克到底有什麽新功能,希望更靈活的來改變,而不是以代碼的形式將它定死在系統中.理解這一層動態與靜態的概念,是整個裝飾模式的重要基礎.

如裝飾模式的意圖中所述,裝飾模式可以動態的加入程序功能,避免因為過度子類化帶來的耦合,相比較用繼承方式的靜態,裝飾更為靈活.我們需要將新加入的三個坦克功能變成動態加入,而非繼承實現.這樣就不用寫一大堆的實現類,同時還需要說明一點,裝飾模式的使用是不會破壞主體類的結構,也就是說Tank類與它的子類們是不會感知到自己被裝飾了,加入了新的功能,這一點至關重要,滿足這一點,才是真的解耦.而繼承方案中,是以侵入的方式強迫Tank的子類們來實現一些與它主職沒什麽關系的內容.這也是違背單一職責原則的.

技術分享圖片

其實軟件模式中最基礎的考慮都是找出變化點,抽象化封裝穩定的部分,讓變化的部分由不同的實現來做,如果發現變化的方向有不只一個,那就要讓它們分別對待,讓各自沿著自己的方向去演變,如上圖所示,我們將新引入的三個坦克功能做了一個抽象基類TankDecorate,代表所有要用來裝飾坦克功能的類的基礎裝飾,同時也是一個容器,可以看到它與Tank之間既有組合也有繼承,這可能會讓大夥感覺到不明其意,就像前邊所講,繼承並不是天生就有錯的,只是用錯了場景才會有問題,這裏的繼承是為了讓TankDecorate能夠知道坦克的原生動作,才能在調用加一層包裝,這一點與AOP面向切面編程時有一點相似.而組合關系則需要合到一個被包裝的Tank的實例.這裏需要用心體會一下,也就是裝飾類既偽裝成坦克,但是並不實再具體內容,只是在其的關鍵業務方法時加入動作而已.

SatellitesTank,ExcavateTank,SilencerTank實現了具體的裝飾功能,而TankTqianqiImpl與TankGuanglengImpl並未有任何修改.

代碼示例

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 坦克基類
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public abstract  class Tank
{
    /**開炮*/
    public abstract void fire() ;
}

坦克基類,定義開火動作.

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 天啟實現
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class TankTianqiImpl extends Tank
{
    @Override
    public void fire ()
    {
        System.out.println ("天啟坦克開炮了");
    }
}

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 光棱實現
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class TankGuanglengImpl extends Tank
{
    @Override
    public void fire ()
    {
        System.out.println ("光棱坦克開炮了");
    }
}

具體坦克的實現,光棱與天啟坦克.

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 裝飾基類
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public abstract class TankDecorate extends  Tank
{
    private Tank tank ;

    public TankDecorate( Tank tank )
    {
        this.tank = tank ;
    }

    public Tank getTank()
    {
        return this.tank;
    }
}

裝飾基類,繼承自Tank,然後組合一個Tank實例,這裏既有Is A同時還有Has a,其實這是很好的協調了is a 與 ha a ,並無沖突,因為這種繼承關系表示的是需要靜態綁定Tank的動作,這一點是穩固的.而has a 解決的部分是動態的,所傳入的tank實例是需要在它之前之後加入我們的裝飾功能的.

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 挖掘裝飾
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class ExcavateTank extends TankDecorate
{
    public ExcavateTank (Tank tank)
    {
        super (tank);
    }

    @Override
    public void fire ()
    {
        System.out.println ("啟動了挖掘功能.");
        this.getTank ().fire ();
    }
}

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 定位裝飾
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class SatellitesTank extends TankDecorate
{
    public SatellitesTank (Tank tank)
    {
        super (tank);
    }

    @Override
    public void fire ()
    {
        System.out.println ("啟動了定位功能.");
        this.getTank ().fire ();
    }
}

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 消音裝飾
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class SilencerTank extends TankDecorate
{
    public SilencerTank (Tank tank)
    {
        super (tank);
    }

    @Override
    public void fire ()
    {
        System.out.println ("啟動了消音功能.");
        this.getTank ().fire ();
    }
}

這三個實現類分別都實現了各自的功能,並且調用原對象的fire方法,這其實就像鏈表中所涉及到的指針一樣,用引用一環套著一環,一個對象持有著下一個對象的引用,同時也有可能被別人持有著自己的引用,讓整個對象型成一個鏈表的結構.只不過我們只體現的部分是加入動作.並未有別的操作.

package com.j2kaka.coolka.examples.pattern.decorate;

/**
 * 客戶端調用代碼
 *
 * @author aladdinty
 * @create 2018-01-30
 **/
public class Client
{
    public static void main(String [] args  )
    {
        //天啟坦克
        Tank tank = new TankTianqiImpl () ;

        //定位裝飾
        SatellitesTank sateTank = new SatellitesTank ( tank ) ;

        //消音裝飾
        SilencerTank sileTank = new SilencerTank ( sateTank ) ;

        sileTank.fire ();


        //光棱坦克
        Tank gtank = new TankGuanglengImpl () ;

        //定位裝飾
        SatellitesTank sateGLTank = new SatellitesTank ( gtank ) ;

        //消音裝飾
        SilencerTank sileGlTank = new SilencerTank ( sateGLTank ) ;

        //挖掘裝飾
        ExcavateTank excaGLTank = new ExcavateTank ( sileGlTank ) ;

        excaGLTank.fire ();



    }
}

調用處代碼可以看到,這裏就可以靈活的組合使用裝飾功能了,一個坦克想消音,還是想挖掘,這些全由調用處來決定,而不需要綁定一個臃腫的類結構了.

動行結果
技術分享圖片

最後總結

說到最後個人感覺,這個裝飾模式就是像一句廣告詞一樣,男人有自己的很多面,今天你要秀出你的哪一面,七匹狼性格男裝... 其實是這樣,對外的表現不同,但核心並未改變,你我都在這個浮華的社會堅持著自己,曾幾何時那個少年望著滿天星發呆,一心想著外面的世界很精彩..不管世界怎麽變,我希望我們的初心不變,最多不過加了幾層裝飾吧.

裝飾模式(Decorate)-今天你要秀你的哪一面?