淺談裝飾模式及其在JDK救军、Flink中的應(yīng)用

前言

上周末在家翻看之前寫的部分文章财异,發(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

明天高考供屉,各位小盆友加油加油。

民那晚安晚安溺蕉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伶丐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疯特,更是在濱河造成了極大的恐慌哗魂,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漓雅,死亡現(xiàn)場離奇詭異录别,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)邻吞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門组题,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抱冷,你說我怎么就攤上這事崔列。” “怎么了徘层?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵峻呕,是天一觀的道長利职。 經(jīng)常有香客問我趣效,道長,這世上最難降的妖魔是什么猪贪? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任跷敬,我火速辦了婚禮,結(jié)果婚禮上热押,老公的妹妹穿的比我還像新娘西傀。我一直安慰自己斤寇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布拥褂。 她就那樣靜靜地躺著娘锁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饺鹃。 梳的紋絲不亂的頭發(fā)上莫秆,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音悔详,去河邊找鬼镊屎。 笑死,一個胖子當(dāng)著我的面吹牛茄螃,可吹牛的內(nèi)容都是我干的缝驳。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼归苍,長吁一口氣:“原來是場噩夢啊……” “哼用狱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霜医,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤齿拂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肴敛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體署海,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年医男,在試婚紗的時候發(fā)現(xiàn)自己被綠了砸狞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡镀梭,死狀恐怖刀森,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情报账,我是刑警寧澤研底,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站透罢,受9級特大地震影響榜晦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羽圃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一乾胶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦识窿、人聲如沸斩郎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缩宜。三九已至,卻和暖如春甥温,著一層夾襖步出監(jiān)牢的瞬間脓恕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工窿侈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炼幔,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓史简,卻偏偏與公主長得像乃秀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子圆兵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351