一、引子
裝飾模式?肯定讓你想起又黑又火的家庭裝修來。其實(shí)兩者在道理上還是有很多相像的地方。家庭裝修無非就是要掩蓋住原來實(shí)而不華的墻面,抹上一層華而不實(shí)的涂料,讓生活多一點(diǎn)色彩。而墻還是那堵墻,他的本質(zhì)一點(diǎn)都沒有變,只是多了一層外衣而已。
那設(shè)計(jì)模式中的裝飾模式,是什么樣子呢?
?
二、定義與結(jié)構(gòu)
裝飾模式(
Decorator
)也叫包裝器模式(
Wrapper
)。
GOF
在《設(shè)計(jì)模式》一書中給出的定義為:動(dòng)態(tài)地給一個(gè)對象添加一些額外的職責(zé)。就增加功能來說,
Decorator
模式相比生成子類更為靈活。
??????
讓我們來理解一下這句話。我們來設(shè)計(jì)
“
門
”
這個(gè)類。假設(shè)你根據(jù)需求為
“
門
”
類作了如下定義:
現(xiàn)在,在系統(tǒng)的一個(gè)地方需要一個(gè)能夠報(bào)警的
Door
,你來怎么做呢?你或許寫一個(gè)
Door
的子類
AlarmDoor
,在里面添加一個(gè)子類獨(dú)有的方法
alarm()
。嗯,那在使用警報(bào)門的地方你必須讓客戶知道使用的是警報(bào)門,不然無法使用這個(gè)獨(dú)有的方法。而且,這個(gè)還違反了
Liskov
替換原則。
也許你要說,那就把這個(gè)方法添加到
Door
里面,這樣不就統(tǒng)一了?但是這樣所有的門都必須有警報(bào),至少是個(gè)
“
啞巴
”
警報(bào)。而當(dāng)你的系統(tǒng)僅僅在一兩個(gè)地方使用了警報(bào)門,這明顯是不合理的
——
雖然可以使用缺省適配器來彌補(bǔ)一下。
??????
這時(shí)候,你可以考慮采用裝飾模式來給門動(dòng)態(tài)的添加些額外的功能。
??????
下面我們來看看裝飾模式的組成,不要急著去解決上面的問題,到了下面自然就明白了!
1)
???????
抽象構(gòu)件角色(
Component
):定義一個(gè)抽象接口,以規(guī)范準(zhǔn)備接收附加責(zé)任的對象。
2)
???????
具體構(gòu)件角色
(Concrete Component)
:這是被裝飾者,定義一個(gè)將要被裝飾增加功能的類。
3)
???????
裝飾角色
(Decorator)
:持有一個(gè)構(gòu)件對象的實(shí)例,并定義了抽象構(gòu)件定義的接口。
4)
???????
具體裝飾角色
(Concrete Decorator)
:負(fù)責(zé)給構(gòu)件添加增加的功能。
看下裝飾模式的類圖:
圖中
ConcreteComponent
可能繼承自其它的體系,而為了實(shí)現(xiàn)裝飾模式,他還要實(shí)現(xiàn)
Component
接口。整個(gè)裝飾模式的結(jié)構(gòu)是按照組合模式來實(shí)現(xiàn)的,但是注意兩者的目的是截然不同的(關(guān)于兩者的不同請關(guān)注我以后的文章)。
?
三、舉例
這個(gè)例子還是來自我最近在研究的
JUnit
,如果你對
JUnit
還不太了解,可以參考
《
JUnit入門》
、
《
JUnit源碼分析(一)》
、
《
JUnit源碼分析(二)》
、
《
JUnit源碼分析(三)》
。不愧是由
GoF
之一的
Erich Gamma
親自開發(fā)的,小小的東西使用了
N
種的模式在里面。下面就來看看
JUnit
中的裝飾模式。
??????
在
JUnit
中,
TestCase
是一個(gè)很重要的類,允許對其進(jìn)行功能擴(kuò)展。
??????
在
junit.extensions
包中,
TestDecorator
、
RepeatedTest
便是對
TestCase
的裝飾模式擴(kuò)展。下面我們將它們和上面的角色對號入座。
??????
呵呵,看看源代碼吧,這個(gè)來的最直接!
?????? //
這個(gè)就是抽象構(gòu)件角色
?????? 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);
}
?
//
具體構(gòu)件對象,但是這里是個(gè)抽象類
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 {
?????? //
這里按照上面的要求,保留了一個(gè)對構(gòu)件對象的實(shí)例
?????? 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;
?????? }
}
?
?????? //
具體裝飾角色,這個(gè)類的增強(qiáng)作用就是可以設(shè)置測試類的執(zhí)行次數(shù)
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)";
??? }
}
??????
使用的時(shí)候,就可以采用下面的方式:
TestDecorator test = new RepeatedTest(new TestXXX() , 3);
讓我們在回想下上面提到的
“
門
”
的問題,這個(gè)警報(bào)門采用了裝飾模式后,可以采用下面的方式來產(chǎn)生。
DoorDecorator alarmDoor = new AlarmDoor(new Door());
?
?
四、應(yīng)用環(huán)境
?????? GOF
書中給出了以下使用情況:
1)
???????
在不影響其他對象的情況下,以動(dòng)態(tài)、透明的方式給單個(gè)對象添加職責(zé)。
2)
???????
處理那些可以撤消的職責(zé)。
3)
???????
當(dāng)不能采用生成子類的方法進(jìn)行擴(kuò)充時(shí)。一種情況是,可能有大量獨(dú)立的擴(kuò)展,為支持每一種組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長。另一種情況可能是因?yàn)轭惗x被隱藏,或類定義不能用于生成子類。
來分析下
JUnit
的使用是屬于哪種情況。首先實(shí)現(xiàn)了比靜態(tài)繼承更加靈活的方式,動(dòng)態(tài)的增加功能。試想為
Test
的所有實(shí)現(xiàn)類通過繼承來增加一個(gè)功能,意味著要添加不少的功能類似的子類,這明顯是不太合適的。
而且,這就避免了高層的類具有太多的特征,比如上面提到的帶有警報(bào)的抽象門類。
?
五、透明和半透明
??????
對于面向接口編程,應(yīng)該盡量使客戶程序不知道具體的類型,而應(yīng)該對一個(gè)接口操作。這樣就要求裝飾角色和具體裝飾角色要滿足
Liskov
替換原則。像下面這樣:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator(c);
JUnit
中就屬于這種應(yīng)用,這種方式被稱為透明式。而在實(shí)際應(yīng)用中,比如
java.io
中往往因?yàn)橐獙υ薪涌谧鎏嗟臄U(kuò)展而需要公開新的方法(這也是為了重用)。所以往往不能對客戶程序隱瞞具體的類型。這種方式稱為
“
半透明式
”
。
在
java.io
中,并不是純裝飾模式的范例,它是裝飾模式、適配器模式的混合使用。
?
六、其它
采用
Decorator
模式進(jìn)行系統(tǒng)設(shè)計(jì)往往會(huì)產(chǎn)生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同,而不是它們的類或是它們的屬性值有所不同。盡管對于那些了解這些系統(tǒng)的人來說,很容易對它們進(jìn)行定制,但是很難學(xué)習(xí)這些系統(tǒng),排錯(cuò)也很困難。這是
GOF
提到的裝飾模式的缺點(diǎn),你能體會(huì)嗎?他們所說的小對象我認(rèn)為是指的具體裝飾角色。這是為一個(gè)對象動(dòng)態(tài)添加功能所帶來的副作用。
?
七、總結(jié)
??????
終于寫完了,不知道說出了本意沒有。請指正!