前言
上周末在家翻看之前寫的部分文章财异,發(fā)現(xiàn)在設(shè)計模式方面甚少涉獵。在閱讀開源項(xiàng)目源碼的過程中唱遭,我們經(jīng)常會接觸到各種設(shè)計模式戳寸,深入理解它們無疑大有裨益,能夠幫助我們快速get到那些masterminds背后的思想拷泽。今天就來談一談應(yīng)用較為廣泛的裝飾模式疫鹊。
裝飾模式與四要素
裝飾模式屬于GoF設(shè)計模式分類中的結(jié)構(gòu)型模式。
所謂裝飾模式(decorator pattern)司致,就是在不改變原有類订晌,也不影響其他繼承自該類的子類的行為的基礎(chǔ)上,為原有類在運(yùn)行期動態(tài)地添加新行為的模式蚌吸。(Attach additional responsibilities to an object dynamically)
我們知道锈拨,類繼承是為類擴(kuò)充功能的一般方案。而裝飾模式作為類繼承的替代方案存在(Decorators provide a flexible alternative to subclassing for extending functionality)羹唠,其意義在于:
類繼承擴(kuò)充的功能在編譯期就被確定奕枢,而裝飾模式擴(kuò)充的功能可以在運(yùn)行時由調(diào)用方確定娄昆。如果要為類同時擴(kuò)充多個相互獨(dú)立而又可以組合的功能,采用類繼承方案就意味著為每種組合創(chuàng)建新的類缝彬,容易造成子類泛濫萌焰。裝飾模式就可以靈活地按需組合(就像現(xiàn)實(shí)中的小裝飾品可以隨意擺放一樣),更加簡潔且易于修改谷浅。
下面的UML類圖示出實(shí)現(xiàn)裝飾模式的四要素扒俯。
- 構(gòu)件(Component):接口,用于定義整個實(shí)體空間的最基礎(chǔ)的行為規(guī)范一疯;
- 構(gòu)件實(shí)體(ConcreteComponent):實(shí)現(xiàn)Component的實(shí)體類撼玄,本身具有一些功能,同時也是被裝飾(被擴(kuò)充)的類墩邀;
- 裝飾器(Decorator):實(shí)現(xiàn)Component的類掌猛,其中維護(hù)一個ConcreteComponent的實(shí)例,具體的裝飾功能由其子類實(shí)現(xiàn)眉睹;
- 裝飾器實(shí)體(ConcreteDecorator):繼承Decorator并實(shí)現(xiàn)具體的裝飾功能荔茬。
通過下面兩句話即可使用裝飾器實(shí)體ConcreteDecorator實(shí)現(xiàn)的擴(kuò)充功能:
Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();
可見,調(diào)用方只需要額外調(diào)用裝飾器實(shí)體的構(gòu)造函數(shù)竹海,而不必關(guān)心Component/ConcreteComponent在裝飾之后的變化慕蔚。不過由上也可以看出,裝飾模式會new出更多的對象斋配,當(dāng)裝飾器實(shí)體的鏈比較長時會有性能問題孔飒,并且出現(xiàn)問題時也不利于debug。
上面所有內(nèi)容講的裝飾模式叫做透明裝飾模式许起,即用戶總可以只用Component來調(diào)用所有功能。相對地菩鲜,還有一種半透明裝飾模式园细,即裝飾器實(shí)體中允許存在Component中不存在的新方法(如someNewBehavior()),調(diào)用方式相應(yīng)就變成:
ConcreteDecorator component = new ConcreteDecorator(new ConcreteComponent());
component.someNewBehavior();
由于擴(kuò)充功能可以在新方法中定義接校,半透明裝飾模式更加靈活猛频,但是就無法對用戶屏蔽ConcreteDecorator存在的現(xiàn)實(shí)了。更重要的是蛛勉,半透明裝飾模式下對實(shí)例進(jìn)行多次(鏈?zhǔn)剑┭b飾是沒有意義的鹿寻,因?yàn)橹荒苷{(diào)用最后一次裝飾時裝飾器實(shí)體的新增方法。
干說了這么多诽凌,舉兩個示例來幫助理解吧毡熏。
Java I/O中的裝飾模式
裝飾模式在java.io包中廣泛使用,包括基于字節(jié)流的InputStream/OutputStream和基于字符的Reader/Writer體系侣诵。以下以InputStream為例痢法。
InputStream是所有字節(jié)輸入流的基類狱窘,其下有眾多子類,如基于文件的FileInputStream财搁、基于對象的ObjectInputStream蘸炸、基于字節(jié)數(shù)組的ByteArrayInputStream等。有些時候尖奔,我們想為這些流加一些其他的小特性搭儒,如緩沖、壓縮等提茁,用裝飾模式實(shí)現(xiàn)就非常方便淹禾。相關(guān)的部分類圖如下所示。
這個類圖很標(biāo)準(zhǔn)甘凭,其中:
- 構(gòu)件是InputStream稀拐;
- 構(gòu)件實(shí)體是FileInputStream、ObjectInputStream等等丹弱;
- 裝飾器是FilterInputStream德撬;
- 裝飾器實(shí)體是FilterInputStream的所有子類。
觀察一下裝飾器FilterInputStream的開頭躲胳,可以發(fā)現(xiàn)它持有InputStream的引用蜓洪,并且實(shí)現(xiàn)了InputStream中的所有方法(實(shí)際上就是簡單地代理了一下)。具體的裝飾器實(shí)體就繼承FilterInputStream坯苹,并實(shí)現(xiàn)對應(yīng)的擴(kuò)充功能隆檀。如下圖所示。
以下就可以用BufferedInputStream和GZIPInputStream創(chuàng)建一個帶緩沖粹湃、壓縮的文件輸入流恐仑。
InputStream is = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
當(dāng)然,如果我們想要自己實(shí)現(xiàn)一個InputStream的裝飾器實(shí)例为鳄,創(chuàng)建一個FilterInputStream的子類即可裳仆,就不再舉例了。
Flink State TTL中的裝飾模式
筆者之前寫過一篇文章《簡析Flink狀態(tài)生存時間(State TTL)機(jī)制的底層實(shí)現(xiàn)》孤钦,這里就用到了裝飾模式歧斟,但不像Java I/O那樣標(biāo)準(zhǔn)。
為狀態(tài)增加TTL的特性可以直接在原始狀態(tài)之上實(shí)現(xiàn)偏形,因此符合裝飾模式的場景静袖。Flink引入了一個AbstractTtlDecorator<T>抽象類作為裝飾器,負(fù)責(zé)為狀態(tài)類型T裝飾上與TTL相關(guān)的基本邏輯俊扭。相關(guān)的部分類圖如下所示队橙。
可見,雖然AbstractTtlDecorator并未持有State的實(shí)例(只有State的類型參數(shù)),但是在其子類AbstractTtlState中喘帚,通過持有TTL狀態(tài)上下文TTLStateContext間接地得到了State實(shí)例畅姊。例如,由AbstractTtlState派生出來的TtlMapState直接在原來的MapState上進(jìn)行增刪改查操作吹由,只是附帶上了AbstractTtlDecorator和AbstractTtlState提供的TTL邏輯而已若未。其他的TtlListState等也是同理。具體的代碼可參見前面給的傳送門倾鲫,這里不再重復(fù)貼了粗合。
雖然這種模式的類結(jié)構(gòu)并不典型,但是也完全契合裝飾模式的精神乌昔,Ttl*State對用戶也是透明的隙疚。有很多開源框架都采用了這種相對松散的裝飾模式,有時會被稱為包裝(Wrapper)模式磕道。
The End
明天高考供屉,各位小盆友加油加油。
民那晚安晚安溺蕉。