軟體設計模式之(二)裝飾者模式
歡迎大家提出意見,一起討論!
例子程式碼:(編譯工具:Eclipse)
參考書籍: <<軟體祕笈-----設計模式那點事>>
裝飾者模式(Decorator ['dekəreitə] Pattren),是在不改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能,它是通過建立一個包裝物件,也就是
裝飾來包裹真實的物件。
使用裝飾者模式的時候需要注意以下幾點內容: (1)裝飾物件和真實物件有相同的介面。這樣客戶端物件就可以以和真實物件相同的方式和裝飾物件互動。 (2)裝飾物件包含一個真實物件的引用。 (3)裝飾物件接受所有來自客戶端的請求,並把這些請求轉發給真實的物件。1、一般化分析
分析一下染色饅頭的製作過程: (1)需要生產一個正常饅頭; (2)為了節省成本(不使用玉米麵),使用染色劑加入到正常饅頭中; (3)通過和麵機攪拌,最後生產出“玉米饅頭”。/** * 饅頭加工介面 * * @author * */ public interface IBread { // 準備材料 public void prepair(); // 和麵 public void kneadFlour(); // 蒸饅頭 public void steamed(); /** * 加工饅頭方法 */ public void process(); }
/** * 正常饅頭的實現 * * @author * */ public class NormalBread implements IBread { // 準備材料 public void prepair() { System.out.println("準備麵粉、水以及發酵粉..."); } // 和麵 public void kneadFlour() { System.out.println("和麵..."); } // 蒸饅頭 public void steamed() { System.out.println("蒸饅頭...香噴噴的饅頭出爐了!"); } /** * 加工饅頭方法 */ public void process() { // 準備材料 prepair(); // 和麵 kneadFlour(); // 蒸饅頭 steamed(); } }
/**
* 染色的玉米饅頭
*
* @author
*
*/
public class CornBread extends NormalBread {
// 黑心商販 開始染色了
public void paint() {
System.out.println("新增檸檬黃的著色劑...");
}
// 過載父類的和麵方法
@Override
public void kneadFlour() {
// 在麵粉中加入 染色劑 之後才開始和麵
this.paint();
// 和麵
super.kneadFlour();
}
}
/**
* 甜蜜素饅頭
*
* @author
*
*/
public class SweetBread extends NormalBread {
// 黑心商販 開始新增甜蜜素
public void paint() {
System.out.println("新增甜蜜素...");
}
// 過載父類的和麵方法
@Override
public void kneadFlour() {
// 在麵粉中加入 甜蜜素 之後才開始和麵
this.paint();
// 和麵
super.kneadFlour();
}
}
我們知道,饅頭的種類是很多的,現在黑心商販又想生產“甜玉米饅頭"了,怎麼辦叱?你可能要說“使用繼承”不就行了嗎? 再建立一個饅頭,繼承正常饅頭類。不可否認,這樣做是不錯的,可以實現要求。然而,反映到類圖上是又多了一個子類,如果還有其他的饅頭種類, 都要繼承嗎?那樣的話類就要爆炸了,龐大的繼承類關係圖。 繼承方式存在這樣兩點不利因素: (1)父類的依賴程式過高,父類修改會影響到子類的行為。 (2)不能複用已有的類,造成子類過多。
2、下面我們使用裝飾者模式重新實現染色饅頭的例項!
使用裝飾者的靜態類圖,結構如圖所示。 (1)為了裝飾正常饅頭NormalBread,我們需要一個正常饅頭一樣的抽象裝飾者:AbstractBread, 該類和正常饅頭類NormalBread一樣實現IBread饅頭介面,不同的是該抽象類含有一個IBread介面型別的私有屬性bread, 然後通過構造方法,將外部IBread介面型別物件傳入。/**
* 抽象裝飾者
*
* @author
*
*/
public abstract class AbstractBread implements IBread {
// 儲存傳入的IBread物件
private final IBread bread;
public AbstractBread(IBread bread) {
this.bread = bread;
}
// 準備材料
public void prepair() {
this.bread.prepair();
}
// 和麵
public void kneadFlour() {
this.bread.kneadFlour();
}
// 蒸饅頭
public void steamed() {
this.bread.steamed();
}
// 加工饅頭方法
public void process() {
prepair();
kneadFlour();
steamed();
}
}
AbstractBread類滿足了裝飾者的要求:和真實物件具有相同的介面;包含一個真實物件的引用;接受所有來自客戶端的請求,並反這些請求轉發給真實的物件;
可以增加一些附加功能。
(2)建立裝飾者。
A、建立染色劑裝飾者----CornDecorator.
建立染色裝飾者"CornDecorator",繼承AbstractBread, 含有修改行為:新增檸檬黃的著色劑。
/**
* 染色的玉米饅頭
*
* @author
*
*/
public class CornDecorator extends AbstractBread {
// 構造方法
public CornDecorator(IBread bread) {
super(bread);
}
// 黑心商販 開始染色了
public void paint() {
System.out.println("新增檸檬黃的著色劑...");
}
// 過載父類的和麵方法
@Override
public void kneadFlour() {
// 在麵粉中加入 染色劑 之後才開始和麵
this.paint();
// 和麵
super.kneadFlour();
}
}
這和上面提到的CornBread類的內容是一樣的,只是多了一個構造方法。 B、建立甜蜜互裝飾者-----SweetDecorator
/**
* 甜蜜素饅頭
*
* @author
*
*/
public class SweetDecorator extends AbstractBread {
// 構造方法
public SweetDecorator(IBread bread) {
super(bread);
}
// 黑心商販 開始新增甜蜜素
public void paint() {
System.out.println("新增甜蜜素...");
}
// 過載父類的和麵方法
@Override
public void kneadFlour() {
// 在麵粉中加入 甜蜜素 之後才開始和麵
this.paint();
// 和麵
super.kneadFlour();
}
}
C、生產甜玉米饅頭。 首先建立一個正常饅頭,然後使用甜蜜素裝飾饅頭,之後再用檸檬黃的著色劑裝飾饅頭,最後加工饅頭。
/**
* 客戶端應用程式
*
* @author
*
*/
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// 生產裝飾饅頭
System.out.println("\n====開始裝飾饅頭!!!");
// 建立普通的正常饅頭例項
// 這是我們需要包裝(裝飾)的物件例項
IBread normalBread = new NormalBread();
// 下面就開始 對正常饅頭進行裝飾了!!!
// 使用甜蜜素裝飾饅頭
normalBread = new SweetDecorator(normalBread);
// 使用檸檬黃的著色劑裝飾饅頭
normalBread = new CornDecorator(normalBread);
// 生產饅頭資訊
normalBread.process();
System.out.println("====裝飾饅頭結束!!!");
}
}
執行結果:
====開始裝飾饅頭!!!
準備麵粉、水以及發酵粉...
新增檸檬黃的著色劑...
新增甜蜜素...
和麵...
蒸饅頭...香噴噴的饅頭出爐了!
====裝飾饅頭結束!!!
可能你會感覺比較奇怪,我們先使用的是甜蜜素,然後使用的是著色劑,應該先列印甜蜜素,然後再列印著色劑才對? 不用奇怪,這也不矛盾,因為裝飾者相當於對原有物件的包裝,這就像一個禮品盒,最裡面是一個最普通的紙盒, 然後用一般的紙包裝起來,最後使用比較漂亮的包裝紙包裝,我們最先看到的是最外漂亮的包裝紙,其次才是裡面的一般包裝紙。
3、設計原則
(1)封裝變化部分
設計模式是封裝變化的最好闡釋,無論哪一種設計模式針對的都是軟體中存在的“變化”部分,然後
用抽象對這些“變化”的部分進行封裝。使用抽象的好處在於為軟體的擴充套件提供了很大的方便性。
在裝飾者模式中合理地利用了類繼承和組合的方式,非常靈活地表達了物件之間的依賴關係。
裝飾者模式應用中“變化”的部分是元件的擴充套件功能,裝飾者和被裝飾者完全隔離開來,這樣我們就可以任意地改變裝飾者和被裝飾者。
(2)"開-閉"原則
我們在需要對元件進行擴充套件、增添新的功能行為時,只需要實現一個特定的裝飾者即可,這完全是增量修改,
對原有軟體功能結構沒有影響,對客戶端APP來說也是完全透明的,不必關心內部實現細節。
(3)面向抽象程式設計,不要面向實現程式設計
在裝飾者模式中,裝飾者角色就是抽象類實現,面向抽象程式設計的好處就在於起到了很好的介面隔離作用。在運用時,
我們具體操作的也是抽象類引用,這些顯示了面向抽象程式設計。
(4)優先使用物件組合,而非類繼承。
裝飾者模式最成功在於合理地使用了物件組合方式,通過組合靈活地擴充套件了元件的功能,所有的擴充套件功能都是通過組合而非繼承獲得的,
這從根本上決定了是高內聚、低耦合的。
4、使用場合
(1)當我們需要為某個現有的物件動態地增加一個新的功能或職責時,可以考慮使用裝飾者模式;
(2)當某個物件的職責經常發生變化或者經常需要動態地增加職責,避免為了適應這樣的變化而增加繼承子類擴充套件的方式,因為這種方式會造成子類膨脹的速度過快,難以控制,此時可以使用裝飾者模式。
客戶端不會覺得物件在裝飾前和裝飾後有什麼不同,裝飾者模式可以在不建立更多子類的情況下,將物件的功能加以擴充套件,裝飾者模式使用原來被裝飾的一個子類例項,
把客戶端的呼叫委派到裝飾者。
下面我們來看一下裝飾者模式的靜態類圖.使我們對裝飾者模式有一個更加清晰的認識
A、被裝飾者抽象Component:是一個介面或抽象類,是定義的核心物件。
在裝飾者模式中,必然有一個被撮出來最核心、最原始的介面。這個類就是我們需要裝飾類的基類。本例中是IBread介面。
B、被裝飾者具體實現ConcreteComponent:這是Component類的一個實現類,我們要裝飾的就是這個具體的實現類。本例中是NormalBread
C、裝飾者Decorator:一般是一個抽象類。它裡面有一個指向Component變數的引用。
D、裝飾者實現ConcreteDecorator1和ConcreteDecorator2.