官术网_书友最值得收藏!

第4章 裝飾模式

4.1 從生活中領(lǐng)悟裝飾模式

4.1.1 故事劇情—你想怎么搭就怎么搭

Tony因?yàn)閾Q工作而搬了一次家!這是一個(gè)4室1廳1衛(wèi)1廚的戶型,住了4戶人家。恰巧這里住的都是年輕人,有男孩也有女孩,而 Tony就是在這里遇上了自己喜歡的人,她叫Jenny。Tony和Jenny每天低頭不見抬頭見,但Tony是一個(gè)程序員,天生不善言辭,不懂著裝,老被Jenny嫌棄:滿臉猥瑣,一副邋遢樣!

被嫌棄后,Tony痛定思痛:一定要改善一下自己的形象!于是叫上自己的死黨Henry一起去了五彩城……

Tony在這個(gè)大商城中兜兜轉(zhuǎn)轉(zhuǎn),被各個(gè)商家教化著該怎樣搭配衣服:襯衫要套在腰帶里面,風(fēng)衣不要系紐扣,領(lǐng)子要立起來……

在反復(fù)試穿了一個(gè)晚上的衣服之后,Tony 終于找到一套還算湊合的著裝:下面是一條卡其色休閑褲配一雙深色休閑皮鞋,加一條銀色針扣頭的黑色腰帶;上面是一件紫紅色針織毛衣,內(nèi)套一件白色襯衫;頭上戴一副方形黑框眼鏡。整體著裝雖不潮流,卻透露出一種工作人士的成熟、穩(wěn)健和大氣!

4.1.2 用程序來模擬生活

服裝店里的衣服品類齊全,款式多樣,但不同品味的人會(huì)搭配出完全不同的風(fēng)格。Tony是一個(gè)程序員,給自己搭配了一套著裝,但類似的著裝也可以穿在其他人身上,比如一個(gè)老師也可以這樣穿。下面我們就用程序來模擬這樣一個(gè)情景。

源碼示例4-1 模擬故事劇情

測試代碼:

上面的測試代碼中decorateTeacher=GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator (Teacher("wells","教授"))))這個(gè)寫法,大家不要覺得奇怪,它其實(shí)就是將多個(gè)對(duì)象的創(chuàng)建過程合在了一起,是一種優(yōu)雅的寫法。創(chuàng)建的Teacher對(duì)象通過參數(shù)傳給LeatherShoesDecorator的構(gòu)造函數(shù),而創(chuàng)建的LeatherShoesDecorator對(duì)象又通過參數(shù)傳給WhiteShirtDecorator的構(gòu)造函數(shù),依此類推……

輸出結(jié)果:

4.2 從劇情中思考裝飾模式

4.2.1 什么是裝飾模式

Attach additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extending functionality.

動(dòng)態(tài)地給一個(gè)對(duì)象增加一些額外的職責(zé),就拓展對(duì)象功能來說,裝飾模式比生成子類的方式更為靈活。

就故事劇情中這個(gè)示例來說,由結(jié)構(gòu)龐大的子類繼承關(guān)系(如圖4-1所示)轉(zhuǎn)換成了結(jié)構(gòu)緊湊的裝飾關(guān)系(如圖4-2所示)。

圖4-1 繼承關(guān)系

圖4-2 裝飾關(guān)系

4.2.2 裝飾模式設(shè)計(jì)思想

在故事劇情中,Tony為了改善自己的形象,換了整體著裝,改變了自己的氣質(zhì),使自己看起來不再是那個(gè)猥瑣的邋遢樣。俗話說一個(gè)人帥不帥,三分看長相,七分看打扮。同一個(gè)人,不一樣的著裝,會(huì)給人完全不一樣的感覺。我們可以任意搭配不同的衣服、圍巾、褲子、鞋子、眼鏡、帽子以達(dá)到不同的效果。

在這個(gè)追求個(gè)性與自由的時(shí)代,穿著的風(fēng)格可謂是開放到了極致,真是你想怎么搭就怎么搭!如果你去參加一個(gè)正式會(huì)議或演講,可以穿一套標(biāo)配西服;如果你去大草原,想騎著駿馬馳騁天地,便該穿上馬服、馬褲、馬鞋;如果你是漫迷,去參加動(dòng)漫節(jié),亦可穿上cosplay的衣服,讓自己成為那個(gè)內(nèi)心向往的主角……

這樣一個(gè)時(shí)時(shí)刻刻發(fā)生在我們生活中的著裝問題,就是程序中裝飾模式的典型樣例。在程序中,我們希望動(dòng)態(tài)地給一個(gè)類增加額外的功能,而不改動(dòng)原有的代碼,就可用裝飾模式來進(jìn)行拓展。

4.3 裝飾模式的模型抽象

4.3.1 類圖

裝飾模式的類圖如圖4-3所示。

圖4-3 裝飾模式的類圖

圖4-3中的Component是一個(gè)抽象類,代表具有某種功能(function)的組件,ComponentImplA和ComponentImplB分別是其具體的實(shí)現(xiàn)子類。Decorator是Component的裝飾器,里面有一個(gè)Component的對(duì)象decorated,這就是被裝飾的對(duì)象,裝飾器可為被裝飾對(duì)象添加額外的功能或行為(addBehavior)。DecoratorImplA和DecoratorImplB分別是兩個(gè)具體的裝飾器(實(shí)現(xiàn)子類)。

這樣一種模式很好地將裝飾器與被裝飾的對(duì)象進(jìn)行了解耦。

4.3.2 Python中的裝飾器

在Python中一切都是對(duì)象:一個(gè)實(shí)例是一個(gè)對(duì)象,一個(gè)函數(shù)也是一個(gè)對(duì)象,甚至類本身也是一個(gè)對(duì)象。在Python中,可以將一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù),也可以將一個(gè)類作為參數(shù)傳遞給一個(gè)函數(shù)。

1.Python中函數(shù)的特殊功能

在 Python 中,函數(shù)可以作為一個(gè)參數(shù)傳遞給另一個(gè)函數(shù),也可以在函數(shù)中返回一個(gè)函數(shù),還可以在函數(shù)內(nèi)部再定義函數(shù)。這是Python和很多靜態(tài)語言不同的地方,這一特性給它帶來了很多新奇的功能。

源碼示例4-2 函數(shù)的特殊功能

輸出結(jié)果如下:

上面的調(diào)用代碼等同于:

2.裝飾器修飾函數(shù)

裝飾器的作用:包裝一個(gè)函數(shù),并改變(拓展)它的行為。

我們以一個(gè)場景為例,看一下Python中裝飾器是如何實(shí)現(xiàn)的。假設(shè)有這樣一個(gè)需求:我們希望每一個(gè)函數(shù)在被調(diào)用之前和被調(diào)用之后,記錄一條日志。

源碼示例4-3 定義裝飾器

輸出結(jié)果:

我們?cè)趌oggingDecorator中定義了一個(gè)內(nèi)部函數(shù)wrapperLogging,用于在傳入的函數(shù)中執(zhí)行前后記錄日志,一般稱這個(gè)函數(shù)為包裝函數(shù),并在最后返回這個(gè)函數(shù)。我們稱loggingDecorator為裝飾器,定義這個(gè)裝飾器函數(shù)之后,就可以將其應(yīng)用于所有希望記錄日志的函數(shù),比如下面這樣一個(gè)函數(shù):

輸出結(jié)果:

有沒有發(fā)現(xiàn),我們每次調(diào)用一個(gè)函數(shù),都要寫兩行代碼。這是非常繁瑣的,Python有沒有更簡單的方式,讓我們的代碼更簡潔一些呢?答案是肯定的,那就是@decorator語法,如下所示:

@loggingDecorator 表示用loggingDecorator裝飾器來修飾showMin函數(shù),它的功能與下面代碼的作用是相同的,但調(diào)用時(shí),只需要寫一行代碼,和調(diào)用一般函數(shù)是一樣的。

3.裝飾器修飾類

裝飾器可以是一個(gè)函數(shù),也可以是一個(gè)類(必須要實(shí)現(xiàn)__call__方法,使其是callable的)。同時(shí)裝飾器不僅可以修改一個(gè)函數(shù),還可以修飾一個(gè)類,示例如下。

源碼示例4-4 修飾類的裝飾器

輸出結(jié)果:

這里 ClassDecorator 是類裝飾器,記錄一個(gè)類被實(shí)例化的次數(shù)。其修飾一個(gè)類和修飾一個(gè)函數(shù)的用法是一樣的,只需在定義類時(shí) @ClassDecorator即可。

4.3.3 模型說明

1.設(shè)計(jì)要點(diǎn)

(1)可靈活地給一個(gè)對(duì)象增加職責(zé)或拓展功能。你可任意地穿上自己想穿的衣服。不管穿上什么衣服,你還是那個(gè)你,但穿上不同的衣服你就會(huì)有不同的外表。

(2)可增加任意多個(gè)裝飾 你可以只穿一件衣服,也可以只穿一條褲子,也可以衣服和褲子搭配著穿,隨你意!

(3)裝飾的順序不同,可能產(chǎn)生不同的效果。在上面的示例中,Tony把針織毛衣穿在外面,白色襯衫穿在里面。當(dāng)然,如果你愿意(或因?yàn)榕吕洌部梢园厌樋椕麓┰诶锩妫咨r衫穿在外面。但兩種著裝穿出來的效果、給人的感覺肯定是完全不一樣的。

使用裝飾模式時(shí),想要改變裝飾的順序,也是非常簡單的。只要把測試代碼稍微改動(dòng)一下即可,如下所示:

輸出結(jié)果如下:

2.裝飾模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

(1)使用裝飾模式來實(shí)現(xiàn)擴(kuò)展比使用繼承更加靈活,它可以在不創(chuàng)造更多子類的情況下,將對(duì)象的功能加以擴(kuò)展。

(2)可以動(dòng)態(tài)地給一個(gè)對(duì)象附加更多的功能。

(3)可以用不同的裝飾器進(jìn)行多重裝飾,裝飾的順序不同,可能產(chǎn)生不同的效果。

(4)裝飾類和被裝飾類可以獨(dú)立發(fā)展,不會(huì)相互耦合;裝飾模式相當(dāng)于繼承的一個(gè)替代模式。

缺點(diǎn):

與繼承相比,用裝飾的方式拓展功能容易出錯(cuò),排錯(cuò)也更困難。對(duì)于多次裝飾的對(duì)象,調(diào)試尋找錯(cuò)誤時(shí)可能需要逐級(jí)排查,較為煩瑣。

3.Python裝飾器與裝飾模式的區(qū)別與聯(lián)系

在“4.3.2 Python中的裝飾器”一節(jié)中講了Python中裝飾器的原理和用法,它與我們?cè)谶@一章講的裝飾模式的設(shè)計(jì)模式有什么區(qū)別呢?二者的區(qū)別如表4-1所示。

表4-1 Python裝飾器與裝飾模式的區(qū)別

二者的聯(lián)系是,設(shè)計(jì)的思想相似,即要達(dá)到的目標(biāo)是相似的:更好的拓展性,以及在不需要做太多代碼變動(dòng)的前提下,增加額外的功能。

4.4 應(yīng)用場景

(1)有大量獨(dú)立的擴(kuò)展,為支持每一種組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長時(shí)。

(2)需要?jiǎng)討B(tài)地增加或撤銷功能時(shí)。

(3)不能采用生成子類的方法進(jìn)行擴(kuò)充時(shí),類的定義不能用于生成子類(如Java中的final類)。

裝飾模式的應(yīng)用場景非常廣泛。如在實(shí)際項(xiàng)目開發(fā)中經(jīng)常看到的過濾器,便可用裝飾模式的方式實(shí)現(xiàn)。如果你是Java程序員,那么你對(duì)I/O中的FilterInputStream和FilterOutputStream一定不陌生,它的實(shí)現(xiàn)其實(shí)就是一個(gè)裝飾模式。FilterInputStream(FilterOutputStream)就是一個(gè)裝飾器,而InputStream(OutputStream)就是被裝飾的對(duì)象。我們看一下創(chuàng)建對(duì)象的過程:

這個(gè)寫法與上面Demo中的decorateTeacher=GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator(Teacher("wells","教授"))))是不是很相似?它們都是用一個(gè)對(duì)象套一個(gè)對(duì)象的方式進(jìn)行創(chuàng)建的。

主站蜘蛛池模板: 洛宁县| 吉安县| 平舆县| 靖边县| 广南县| 邢台市| 东山县| 垫江县| 沧源| 社旗县| 桑日县| 五台县| 玉环县| 玉山县| 南昌市| 崇仁县| 凤城市| 邳州市| 武义县| 荆州市| 沂源县| 凉城县| 雷山县| 贵定县| 凭祥市| 沾益县| 屏南县| 涟水县| 盐边县| 赣州市| 积石山| 井研县| 凌源市| 沂源县| 大兴区| 攀枝花市| 夹江县| 云霄县| 同仁县| 广饶县| 罗田县|