設計模式系列都是學習HeadFirst設計模式得出的學習心得,中間的例子也會採用書中的例子。這裡有必要解釋一下,在下麵星巴克咖啡的例子中,有幾種基本的咖啡,還有牛奶、豆漿等等可以向咖啡中添加,這裡說明防止下麵不懂。 今天我們來瞭解一下裝飾者模式。 回想一下java的io包,各種stream排...
設計模式系列都是學習HeadFirst設計模式得出的學習心得,中間的例子也會採用書中的例子。這裡有必要解釋一下,在下麵星巴克咖啡的例子中,有幾種基本的咖啡,還有牛奶、豆漿等等可以向咖啡中添加,這裡說明防止下麵不懂。
今天我們來瞭解一下裝飾者模式。
回想一下java的io包,各種stream排上倒海,初學者根本分不清楚到底怎麼用,眼花繚亂。其實,它的實驗遵循了裝飾者設計模式。顧名思義,裝飾者就可以簡單的理解成用一個東西來裝飾另一個東西。比如,你要做魚吃,在你做好出國之後需要加入香菜。我們就可以簡單的看成雨是一個東西,我們在用香菜裝飾它。錶面上來看,裝飾的作用就是使更加好吃(我們的類更加好用)。其實更重要的是可以隨意的擴展你的程式,還是魚的例子,可能你家有不吃香菜的人,那麼你就不能加入香菜,可能是加入蔥花等等,或者兩者都加入,這樣我們就做成了香菜魚、蔥花魚(根據不同的材料形成不同的產品)。這樣,軟體的靈活性大大提高了。
先看一下java.io包裡面的類:
現在想想java.io包里的類,是不是經常遇到這樣的情況:把一個流作為另一個流構造的參數,相當於用一個類把另一個類包裝起來了。比如
new BufferedInputStream(new InputStream())
這是得到的對象就有了更加強大的功能,因為增加了一個緩衝區,可以一行一行的讀入數據。這就是用java中對於裝飾者模式的典型應用。
我們還是從一個簡單的小例子開始。星巴克咖啡可以提供多種咖啡,如:HouseBlend、DarkRoast、Decaf、Espresso。在這些咖啡里還可以添加:牛奶、豆漿、摩卡等等。想象我們怎樣設計這樣一個系統(當然要算出最後的每一杯的價錢)?他們最開始設計的框架是這樣的:
難道是把每一個咖啡都設計成一個類?想象一下隨機組合的情況,那組合的情況實在太多了,明顯不是一個好的方法。還有一個方法,就是在材料類(介面)中加入所有材料的Blooean欄位,以true代表有這個材料,false代表沒有這個材料(牛奶等)。這樣類的數目就變少了是需要初始化的時候初始化那些欄位就可以了。但是,這樣同時也會帶來一些問題,比如茶不要牛奶等等的添加成分,但是茶的具體實例中卻有這些成員,還有就是如果新增加添加材料呢,所有的類都要添加這個欄位的初始化。到這裡我們都是假定的所有的東西價格不變。如果價格改變了呢?所以這裡問題還是很多的,這不是一個好的解決辦法。
我們現在有什麼好的辦法呢?想象一下我們曾經有一個設計模式的規則,多用組合少用繼承。在結合java.io包裝的形式,我們是不是可以把基本的咖啡以及各種添加的材料看成不同的類,然後我們用組合。但是又不是我們一般的直接組合,設想一下如果按照一般組合,是不是跟第一方法一樣,會造成類爆炸,材料多了以後需要大量的組合?那麼現在我們就直接說裝飾者模式了。如果顧客想要DarkRoast,想加入Mocha,還想要Whip,是不是這樣的過程:
需要添加什麼就用什麼包裝已存在的組件(我們的背包裝的對象成為組件)。我們的框架是這樣的:
這裡的實現代碼為:
Beverage:
public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } public abstract double cost(); }
Condiment:
public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }
HouseLend:
public class HouseBlend extends Beverage { public HouseBlend() { description = "House Blend Coffee"; } public double cost() { return .89; } }
Mocha:
public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Mocha"; } public double cost() { return .20 + beverage.cost(); } }
測試代碼:
public class StarbuzzCoffee { public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }
其餘的類參考上面的,沒有任何變化。
裝飾者模式的重點在於:裝飾者和被裝飾者必須是一樣的類型,也就是有共同的超類,這是相當關鍵的地方。在這裡,我們利用繼承達到“類型匹配”,而不是利用繼承得到行為。有沒有發現,這裡是“運行時”的,而不是編譯時靜態決定。我們可以在運行的時候隨意的組合成任意的類型的咖啡。
下麵我們有一個自己的java I/O裝飾者(只有兩個類):
package com.worsun.javaIO裝飾者; import java.io.*; public class InputTest { public static