昨天面試中又問到裝飾者模式,這次不像一年前那樣一頭霧水什么都說不上來。但是回答得也很一般,我說就像JDK中IOStream一樣,如果使用繼承,會導致非常多的子類,并且java是單繼承,非常不方便;而給對象添加上不同的裝飾物,但是其原本的調用風格不變云云。其實我腦子閃過的是這樣的代碼
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("fileName")));當面試者要求我畫出裝飾者模式的類圖時,我說忘記了,畫不出來。確實,看過很多遍裝飾者模式的書籍、技術帖子,但是過一段時間又容易忘記。最根本原因是沒有理解到位,求大求全,各種模式一一瀏覽,卻沒有深入使用。因此本文呼之欲出,不為教導他人,更多的是記錄自己遇到的問題,記錄自己收集整理并分析資料和加深理解的過程。
查詢資料后,很容易畫出裝飾者模式的UML類圖,如下。裝飾者模式的書面定義是:“動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾模式相比生成子類更為靈活?!薄Z出《Android源碼設計模式解析與實戰》 想起易中天先生的話,“那么我們不禁要問”,誰是那個對象?誰給它增加了額外的職責?繼續翻書、刷帖子,發現:那個對象就是圖中的ConcreteComponent,它原本的職責是Operate(),ConcreteDecoratorA給它增加了operateA1()、operateA2()兩個額外的職責??吹竭@里,你以為我要看圖說話,根據UML類圖給自己解釋裝飾者模式的原理了。
No,我想到另一個問題。為什么所有的書籍,所有的技術博客都直接告訴你,裝飾者模式整個組成就是這樣?為什么一上來你就知道,有一個抽象類或者接口叫Component,然后有一個實現類ConcreteComponent?或者說,裝飾者模式的應用中,是先有什么,后有什么?許許多多的技術博客都會從“裝飾”本身這個詞入手,將人穿衣裝飾為例子進行講解。也就是首先有個抽象類Person,該類有個抽象方法dress(),然后有個子類Xiaoming,繼承Person實現dress()方法云云。 但是正常人的思維順序,或者說沒有接觸過裝飾者模式深圳不熟悉設計模式的人,他的第一反應是:首先,我有一個對象XXX,他有個dress()的動作,后來根據業務需要,我要想辦法給XXX增加一些職責,給這個人增加裝飾。然后人的第一反應是繼承,XXX1,XXX2。。。有多少種穿衣打扮方式就有多少種子類。于是,這種方式明顯不合適。我們想到要用面向對象的封裝和多態來解決這個問題。首先要引入的是面向接口的編程,就是我們要經常地想到,要將一個業務的定義和實現進行分離。比如人的穿衣這件事,分兩個部分來完成。
public interface iperson{ public void dress();}public class PersonImpl implements IPerson{ @Override public void dress(){ ...//方法體實現 } //其他方法}第一個是定義一個接口(或者抽象類)IPerson,將人的共通的特性放在里面,而某些特定的特性放在其實現類PersonImpl中。怎么樣,是不是很有Java EE 搭建框架的感覺?這樣做多了一個類,代碼增加了,但是大有裨益。一個顯而易見的好處是,當你在某處拿到一個IPerson引用時,你的IDE的代碼提示功能只會提示你IPerson里面的方法,不會提示你PersonImpl中的所有方法,這在API級別屏蔽了一些細節。 于是我們順理成章地理解了,最初的最初,我們只有左上角那兩個藍色的類。一個抽象類,一個是這個類的具體實現,至于叫什么并不重要。解決了本體(人)的問題,接下來關注裝飾者(衣物)部分。同樣的道理,裝飾物有很多,會抽取出共通的部分,放到基類中。實際使用時并不會直接使用基類,因此上面的UML類圖中的Decorator通常也定義為抽象類,不讓它實例化。裝飾物Decorator需要繼承本體抽取出來的抽象類Component(或者實現Component接口),同時又保存一個對本體的引用。聽起來很像代理模式?是的,很像很像。只是代理模式是在代理類中實例化被代理者對象,而裝飾者模式是在構造函數中傳入本體對象作為引用;代理模式重點在隱藏被代理者的具體內容,而裝飾者重點在制造出一系列可插拔的裝飾物并且不改變本體的行為 ,關注點不同。
回到引言中提到的代碼,BufferedInputStream相當于裝飾者模式UML類圖中的ConcreteDecoratorA這個角色,而FileInputStream相當于ConcreteComponent角色。Decorator角色的正確理解是:它并不是純粹的裝飾者,它是裝飾本體后的對象。按人-衣物的例子來說,Decorator表示穿上某件衣服飾品的人,而不是飾品本身,這和實際生活有細微的差別。實際生活中,裝飾品可以單獨存在。你穿,或者不穿,衣服飾品就在那,不多不少。而裝飾者模式中,裝飾者這個角色必須要在構造函數中傳入一個本體,即它所裝飾的東西,要不然它沒有什么實際意義。ConcreteComponent是你,ConcreteDecoratorA是穿上衣服A的你,而不是A那件衣服本身。ConcreteDecoratorA是為“可穿衣服的的東西”量身定做,它可以給你穿,給路人甲穿,甚至給狗穿。但是不能給樹穿給天空穿,盡管實際生活中可以。
當我參照資料畫出下面的UML類圖,我被自己嚇了一跳!我們在Android開發中每天都見到的Activity原來是個裝飾者,其裝飾的本尊是ContextImpl,那些常用的方法,諸如startActivity,是在ContextImpl中具體實現的。而且不止有Activity,application和Service都是ContextImpl的裝飾者,因此你可以在Application中啟動一個Activity,也可以在Service中啟動。 Context這個Android開發中的上帝對象,在本文中就不展開論述。文末附上裝飾者模式UML類圖中的部分類的源碼,以供參考。
新聞熱點
疑難解答