一、引子
裝飾模式?肯定讓你想起又黑又火的家庭裝修來。其實兩者在道理上還是有很多相像的地方。家庭裝修無非就是要掩蓋住原來實而不華的墻面,抹上一層華而不實的涂料,讓生活多一點色彩。而墻還是那堵墻,他的本質一點都沒有變,只是多了一層外衣而已。
那設計模式中的裝飾模式,是什么樣子呢?
?
二、定義與結構
裝飾模式(
Decorator
)也叫包裝器模式(
Wrapper
)。
GOF
在《設計模式》一書中給出的定義為:動態地給一個對象添加一些額外的職責。就增加功能來說,
Decorator
模式相比生成子類更為靈活。
??????
讓我們來理解一下這句話。我們來設計
“
門
”
這個類。假設你根據需求為
“
門
”
類作了如下定義:
現在,在系統的一個地方需要一個能夠報警的
Door
,你來怎么做呢?你或許寫一個
Door
的子類
AlarmDoor
,在里面添加一個子類獨有的方法
alarm()
。嗯,那在使用警報門的地方你必須讓客戶知道使用的是警報門,不然無法使用這個獨有的方法。而且,這個還違反了
Liskov
替換原則。
也許你要說,那就把這個方法添加到
Door
里面,這樣不就統一了?但是這樣所有的門都必須有警報,至少是個
“
啞巴
”
警報。而當你的系統僅僅在一兩個地方使用了警報門,這明顯是不合理的
——
雖然可以使用缺省適配器來彌補一下。
??????
這時候,你可以考慮采用裝飾模式來給門動態的添加些額外的功能。
??????
下面我們來看看裝飾模式的組成,不要急著去解決上面的問題,到了下面自然就明白了!
1)
???????
抽象構件角色(
Component
):定義一個抽象接口,以規范準備接收附加責任的對象。
2)
???????
具體構件角色
(Concrete Component)
:這是被裝飾者,定義一個將要被裝飾增加功能的類。
3)
???????
裝飾角色
(Decorator)
:持有一個構件對象的實例,并定義了抽象構件定義的接口。
4)
???????
具體裝飾角色
(Concrete Decorator)
:負責給構件添加增加的功能。
看下裝飾模式的類圖:
圖中
ConcreteComponent
可能繼承自其它的體系,而為了實現裝飾模式,他還要實現
Component
接口。整個裝飾模式的結構是按照組合模式來實現的,但是注意兩者的目的是截然不同的(關于兩者的不同請關注我以后的文章)。
?
三、舉例
這個例子還是來自我最近在研究的
JUnit
,如果你對
JUnit
還不太了解,可以參考
《
JUnit入門》
、
《
JUnit源碼分析(一)》
、
《
JUnit源碼分析(二)》
、
《
JUnit源碼分析(三)》
。不愧是由
GoF
之一的
Erich Gamma
親自開發的,小小的東西使用了
N
種的模式在里面。下面就來看看
JUnit
中的裝飾模式。
??????
在
JUnit
中,
TestCase
是一個很重要的類,允許對其進行功能擴展。
??????
在
junit.extensions
包中,
TestDecorator
、
RepeatedTest
便是對
TestCase
的裝飾模式擴展。下面我們將它們和上面的角色對號入座。
??????
呵呵,看看源代碼吧,這個來的最直接!
?????? //
這個就是抽象構件角色
?????? public interface Test {
?????? /**
??????
?* Counts the number of test cases that will be run by this test.
??????
?*/
?????? public abstract int countTestCases();
?????? /**
??????
?* Runs a test and collects its result in a TestResult instance.
??????
?*/
?????? public abstract void run(TestResult result);
}
?
//
具體構件對象,但是這里是個抽象類
public abstract class TestCase extends Assert implements Test {
?????? ……
?????? public int countTestCases() {
????????????? return 1;
?????? }
?????? ……
?????? public TestResult run() {
????????????? TestResult result= createResult();
????????????? run(result);
????????????? return result;
?????? }
?????? public void run(TestResult result) {
????????????? result.run(this);
?????? }
?????? ……
}
?
//
裝飾角色
public class TestDecorator extends Assert implements Test {
?????? //
這里按照上面的要求,保留了一個對構件對象的實例
?????? protected Test fTest;
?
?????? public TestDecorator(Test test) {
????????????? fTest= test;
?????? }
?????? /**
??????
?* The basic run behaviour.
??????
?*/
?????? public void basicRun(TestResult result) {
????????????? fTest.run(result);
?????? }
?????? public int countTestCases() {
????????????? return fTest.countTestCases();
?????? }
?????? public void run(TestResult result) {
????????????? basicRun(result);
?????? }
?????? public String toString() {
????????????? return fTest.toString();
?????? }
?????? public Test getTest() {
????????????? return fTest;
?????? }
}
?
?????? //
具體裝飾角色,這個類的增強作用就是可以設置測試類的執行次數
public class RepeatedTest extends? TestDecorator {
??? private int fTimesRepeat;
?
??? public RepeatedTest(Test test, int repeat) {
?????????? super(test);
?????????? if (repeat < 0)
????????????????? throw new IllegalArgumentException("Repetition count must be > 0");
?????????? fTimesRepeat= repeat;
??? }
??? //
看看怎么裝飾的吧
??? public int countTestCases() {
?????????? return super.countTestCases()*fTimesRepeat;
??? }
??? public void run(TestResult result) {
?????????? for (int i= 0; i < fTimesRepeat; i++) {
????????????????? if (result.shouldStop())
???????????????????????? break;
????????????????? super.run(result);
?????????? }
??? }
??? public String toString() {
?????????? return super.toString()+"(repeated)";
??? }
}
??????
使用的時候,就可以采用下面的方式:
TestDecorator test = new RepeatedTest(new TestXXX() , 3);
讓我們在回想下上面提到的
“
門
”
的問題,這個警報門采用了裝飾模式后,可以采用下面的方式來產生。
DoorDecorator alarmDoor = new AlarmDoor(new Door());
?
?
四、應用環境
?????? GOF
書中給出了以下使用情況:
1)
???????
在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
2)
???????
處理那些可以撤消的職責。
3)
???????
當不能采用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴展,為支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用于生成子類。
來分析下
JUnit
的使用是屬于哪種情況。首先實現了比靜態繼承更加靈活的方式,動態的增加功能。試想為
Test
的所有實現類通過繼承來增加一個功能,意味著要添加不少的功能類似的子類,這明顯是不太合適的。
而且,這就避免了高層的類具有太多的特征,比如上面提到的帶有警報的抽象門類。
?
五、透明和半透明
??????
對于面向接口編程,應該盡量使客戶程序不知道具體的類型,而應該對一個接口操作。這樣就要求裝飾角色和具體裝飾角色要滿足
Liskov
替換原則。像下面這樣:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator(c);
JUnit
中就屬于這種應用,這種方式被稱為透明式。而在實際應用中,比如
java.io
中往往因為要對原有接口做太多的擴展而需要公開新的方法(這也是為了重用)。所以往往不能對客戶程序隱瞞具體的類型。這種方式稱為
“
半透明式
”
。
在
java.io
中,并不是純裝飾模式的范例,它是裝飾模式、適配器模式的混合使用。
?
六、其它
采用
Decorator
模式進行系統設計往往會產生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同,而不是它們的類或是它們的屬性值有所不同。盡管對于那些了解這些系統的人來說,很容易對它們進行定制,但是很難學習這些系統,排錯也很困難。這是
GOF
提到的裝飾模式的缺點,你能體會嗎?他們所說的小對象我認為是指的具體裝飾角色。這是為一個對象動態添加功能所帶來的副作用。
?
七、總結
??????
終于寫完了,不知道說出了本意沒有。請指正!