設計模式之裝飾模式

broadviewbj發表於2012-04-11

設計模式之裝飾模式

裝飾模式非常強調實現技巧,我們一般用它應對類體系快速膨脹的情況。

在專案中,是什麼原因導致型別體系會快速膨脹呢?在多數情況下是因為我們經常要為型別增加新的職責(功能),尤其在軟體開發和維護階段,這方面需求更為普遍。

物件導向中每一個介面代表我們看待物件的一個特定方面。在Java編碼實現過程中由於受到單繼承的約束,我們通常也會將期望擴充套件的功能定義為新的介面,進而隨著介面不斷增加,實現這些介面的子類也在快速膨脹,如新增3個介面的實現,就需要8個型別(包括MobileImpl),4個介面則是16個型別,這種幾何基數的增長我們承受不了。為了避免出現這種情況,之前我們會考慮採用組合介面的方式解決,但客戶程式又需要從不同角度看待組合後的型別,也就是可以根據里氏替換原則用某個介面呼叫這個子類。所以面臨的問題是,既要has a、又要is a,裝飾模式解決的就是這類問題。

經典回顧

裝飾模式的意圖非常明確:動態為物件增加新的職責。

 

這裡有兩個關鍵詞:動態和增加,也就是說,這些新增的功能不是直接從父類繼承或是硬編碼寫進去的,而是在執行過程中透過某種方式動態組裝上去的。例如:我們在錄入文字的時候,最初只需要一個Notepad功能的軟體,然後增加了很多需求

字型可以加粗。

文字可以顯示為不同顏色。

字號可以調整。

字間距可以調整。

……

不僅如此,到底如何使用這些新加功能,需要在客戶使用過程中進行選擇,也就是說,新的職責(功能或成員)是需要動態增加的。為了解決這類問題,裝飾模式給出的解決方案如圖12-1所示。

 設計模式之裝飾模式

 

根據Notepad的示例要求,設計如圖12-2所示的靜態結構。

 

 設計模式之裝飾模式

 

區別於經典裝飾模式設計,在圖12-2中我們將Decorator定義為具體類,而不是抽象類,主要是為了簡化示例結構。

首先,我們需要的是顯示文字,這時可以指定一個名為Text的介面,它有名為Content的讀/寫屬性。

然後,我們把所有需要用來裝飾的型別抽象出一個名為Decorator的介面,它繼承自這個Text,因此其實體類必須實現Content屬性方法。

接著,我們把沒有任何新增功能的Text做出一個“毛坯”的實體型別,命名為TextImpl

最後,我們把操作字型bold()setColor()的方法填充到每個具體的裝飾型別中。

這樣,概念上當TextImpl需要某種新增功能時,直接為其套上某個具體裝飾型別就可以了。這就好像給TextImpl類穿上一層又一層的“馬甲”。該模式這麼做主要是為了適用於哪些情景呢?

在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。

畢竟客戶程式依賴的僅僅是Component介面,至於這個介面被做過什麼裝飾,只有實施裝飾的物件才知道,而客戶程式只是依據Component的介面方法呼叫它,這樣,裝飾類在給Component穿上新“馬甲”的同時也會隨著客戶程式的呼叫一併執行了。

遮蔽某些職責,也就是在套用某個裝飾型別時,並不增加新的特徵,只把既有方法遮蔽。

也就是說,裝飾類不僅能否充當“馬甲”,也能起到“口罩”的作用,讓Component現有的某些方法“閉嘴”。儘管我們使用裝飾模式一般是為了增加功能(做“加法”),但並不排斥它也具有方法遮蔽的作用(做“減法”),只不過平時用的比較少而已。

避免因不斷調整職責導致型別快速膨脹的情況。

下面看一段示例程式碼:

Java  抽象部分

public interface Text {

    String getContent();

    void setContent(String content);

}

 

/** implements Text 說明Decorator is a Text*/

public class Decorator implements Text{

 

    /** 構造方式注入has atext介面 */

    private Text text;

    public Decorator(Text text){

        this.text = text;

    }

 

    @Override

    public String getContent(){

        return text.getContent();

    }

   

    @Override

    public void setContent(String content){

        text.setContent(content);  

    }

}

Java  具體裝飾型別

 

/**

 * 具體裝飾類,屬於“馬甲”

 */

class BoldDecorator extends Decorator{

   

    public BoldDecorator(Text text){

        super(text);

    }

 

    public String bold(String data){

        return "" + data + "";

    }

   

    @Override

    public String getContent() {   

        return bold(super.getContent());

    }

 

    @Override

    public void setContent(String content) {

        super.setContent(content);

    }

   

}

 

/**

 * 具體裝飾類

 * 屬於“馬甲”

 */

class ColorDecorator extends Decorator{

   

    public ColorDecorator(Text text){

        super(text);

    }

   

    public String setColor(String data){

        return "" + data + "";  

    }

 

    @Override

    public String getContent() {   

        return setColor(super.getContent());

    }

 

    @Override

    public void setContent(String content) {

        super.setContent(content);

    }

   

}

 

/**

 * 具體裝飾類

 * 屬於“口罩”

 */

class BlockAllDecorator  extends Decorator{

   

    public BlockAllDecorator (Text text){

        super(text);

    }

 

    @Override

    public String getContent() {   

        return null;

    }

 

    @Override

    public void setContent(String content) {

        super.setContent(content);

    }

   

}

Unit Test

Text text;

 

@Before

public void setUp(){

    text = new TextImpl();

}

 

/** 驗證套用單個裝飾物件的效果 */

@Test

public void testSingleDecorator(){

    text = new Decorator(text);

   

    //下面開始套用裝飾模式,開始給text穿“馬甲”

    text = new BoldDecorator(text);

    text.setContent("H");

    assertEquals("H", text.getContent());

}

 

/** 驗證套用多個裝飾物件的效果 */

@Test

public void testMultipleDecorators(){

    text = new Decorator(text);

   

    //下面開始套用裝飾模式,開始給text穿“馬甲”

    text = new BoldDecorator(text);

    text = new ColorDecorator(text);

    text = new BoldDecorator(text);

    text.setContent("H");

    assertEquals("H",text.getContent());

}

 

/** 驗證裝飾型別的撤銷(/“口罩”)效果*/

@Test

public void testBlockEffectDecorator(){

    text = new Decorator(text);

   

    //下面開始套用裝飾模式,開始給text穿“馬甲”

    text = new BoldDecorator(text);

    text = new ColorDecorator(text);

    text.setContent("H");

    assertEquals("H", text.getContent());

   

    //下面開始套用裝飾模式,開始給text戴“口罩”

    text = new BlockAllDecorator(text);

    assertNull(text.getContent());

}

從上面的示例中不難看出,裝飾模式實現上特別有技巧,也很“八股”,它的宣告要實現Component定義的方法,但同時也會保留一個對Component的引用,Component介面方法的實現其實是透過自己儲存的Component成員完成的,而裝飾類只是在這個基礎上增加一些額外的處理。而且,使用裝飾模式不僅僅是為了“增加”新的功能,有時候我們也用它“撤銷”某些功能。專案中我們有3個要點必須把握:

Component不要直接或間接地使用Decorator,因為它不應該知道Decorator的存在,裝飾模式的要義在於透過外部has a + is a的方式對目標型別進行擴充套件,對於待裝飾物件本身不應有太多要求。

Decorator也僅僅認識Component抽象依賴於抽象、知識最少。

某個ConcreteDecorator最好也不知道ComponentImpl的存在,因為ConcreteDecorator只能服務於這個ComponentImpl(及其子類)。

此外,使用裝飾模式解決一些難題的同時,我們也要看到這個模式的缺點:

開發階段需要編寫很多ConcreteDecorator型別。

執行時動態組裝帶來的結果就是排查故障比較困難,從實際角度看,Component提交給客戶程式的是最外層ConcreteDecorator的型別,但它的執行過程是一系列ConcreteDecorator處理後的結果,追蹤和除錯相對困難。

在實際專案中,我們往往會將一些通用的功能做成裝飾型別單獨編譯,而且一般也鼓勵這麼做,因為可以減少重複開發,但這樣會人為增加排查和除錯的難度。好在反編譯Java byte code不是太難。

 

本文節選自《模式——工程化實現及擴充套件(設計模式Java 版)》一書。

本書詳細資訊:http://space.itpub.net/?uid-13164110-action-viewspace-itemid-720925

 

設計模式之裝飾模式1.jpg

設計模式之裝飾模式2.jpg

設計模式之裝飾模式模式——工程化實現及擴充套件(設計模式Java 版).jpg

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13164110/viewspace-720926/,如需轉載,請註明出處,否則將追究法律責任。

相關文章