Head first設計模式(3)

裝飾者模式編程

1、我曾經覺得男子漢應該用繼承處理一切,後來我領教到運行時擴展,遠比編譯時期的繼承威力大,看看我如今光彩的樣子設計模式

2、「給愛用繼承的人一個全新的設計眼界」,咱們即將再度討論典型的繼承濫用問題,如何使用對象組合的方式,作到在運行時裝飾類。爲何呢?一旦你熟悉了裝飾的技巧,你將可以在不修改任何底層代碼的狀況下,給你的(或別人的)對象賦予新的職責學習

 

星巴茲咖啡ui

1、這是一個以擴張速度最快而聞名的咖啡連鎖店spa

2、由於擴張速度實在太快了,他們準備更新訂單系統,以合乎他們的飲料供應要求設計

 

原先的類設計是這樣的component

1Beverage(飲料)是一個抽象類,店內所提供的飲料都必須繼承自此類對象

2、這個名爲description(敘述)的實例變量,由每一個子類設置,用來描述飲料,例如「超優深焙(Dark Roast)咖啡豆」繼承

3cost()方法是抽象的子類必須定義本身的實現接口

4、每一個子類實現cost()來返回飲料的價錢

 

需求來了

購買咖啡時,也能夠要求在其中加入各類調料,例如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha,也就是巧克力風味)或覆蓋奶泡。星巴茲會根據所加入的調料收取不一樣的費用。因此訂單系統必須考慮到這些調料部分

這是第一次嘗試

1、每一個cost()方法將計算出咖啡加上訂單上各類調料的價錢

2、哇塞!這簡直是「類爆炸」

3、很明顯,星巴茲爲本身製造了一個維護噩夢。若是牛奶的價錢上揚,怎麼辦?新增一種焦糖調料風味時,怎麼辦?

 

是呀,幹嗎這幾這麼多類?利用實例變量和繼承,就能夠追蹤這些調料呀!

好吧!就來試試看,先從Beverage基類下手,加上實例變量表明是否加上調料(牛奶、豆漿、摩卡、奶泡... ...)

1、各類調料的新的布爾值

2、如今,Beverage類中的cost()再也不是一個抽象方法,咱們提供了cost()的實現,讓它計算要加入各類飲料的調料價錢。子類仍將覆蓋cost(),可是會調用超類的cost(),計算出基本飲料加上調料的價錢

3、這些方法取得和設置調料的布爾值

1、如今加入子類,每一個類表明菜單上的一種飲料

2、超類cost()將計算全部調料的價錢,而子類覆蓋過的cost()會擴展超類的功能,把指定的飲料類型的價錢也加進來

3、每一個cost()方法須要計算該飲料的價錢,而後經過調用超類的cost()實現,加入調料的價錢

 

經過思考設計未來可能須要的變化,能夠看出來這種方法有一些潛在的問題

1、調料價錢的改變會使咱們更改現有代碼

2、一旦出現新的調料,咱們就須要加上新的代碼,並改變超類中的cost()方法

3、之後可能開發出新的飲料,對這些飲料而言(例如:冰茶),某些調料可能並不適合,可是在這個設計方式中,Tea()子類仍將繼承那些不適合的方法,例如:hasWhip()(加奶泡)

4、萬一顧客想要雙倍摩卡咖啡,怎麼辦?

 

大師和門徒

大師:我說蚱蜢呀!舉例咱們上次見面已經有些時日,你對於繼承的冥想,可有精進?

門徒:是的,大師。儘管繼承威力強大,可是我體會到它並不老是可以實現最優彈性和最好維護的設計

大師:啊!是的,看來你已經有所長進。那麼,告訴我,個人門徒,不經過繼承又能如何達到複用呢?

門徒:大師,咱們已經瞭解到利用組合(composition)和委託(delegation)能夠在運行時具備繼承行爲的效果

大師:好,好,繼續... ...

門徒:利用繼承設計子類的行爲,是在編譯時靜態決定的,並且全部的子類都會繼承到相同的行爲。然而,若是可以利用組合的作法擴展對象的行爲,就能夠在運行時動態地進行擴展

大師:很好,蚱蜢,你已經開始看到組合得爲例了

門徒:是的,我能夠利用此技巧把多個新職責,甚至是設計超類時尚未想到的職責加在對象上。並且,能夠不用修改原來的代碼

大師:利用組合維護代碼,你認爲效果如何?

門徒:這正是我要說的。經過動態地組合對象,能夠寫新的代碼添加新功能,而無需修改現有代碼。既然沒有改變現有代碼,那麼引進bug或產生之外反作用的機會將大幅度減小

大師:很是好。蚱蜢今天的談話就到這裏。但願你能在這個主題上更深刻... ... 牢記,代碼應該如同晚霞中的睡蓮同樣地關閉(免於改變),如同晨曦中的蓮花同樣地開放(可以擴展)

 

開放——關閉原則

1、設計原則——類應該對擴展開放,對修改關閉

2、請進,如今"開放"中,歡迎用任何你想要的行爲來擴展咱們的類。若是你的須要或需求有所改變(咱們知道這必定會發生的),那就來吧!動手擴展吧!

3、抱歉,如今是"關閉"狀態。沒錯。咱們花了許多時間獲得了正確的代碼,還解決了全部的bug,因此不能讓你修改現有的代碼。咱們必須關閉代碼以防止被修改。若是你不喜歡,能夠找經理談

4、咱們的目標是容許類容易擴展,在不修改現有代碼的狀況下,就可搭配新的行爲。如能實現這樣的目標,有什麼好處呢?這樣的設計具備彈性能夠應對改變,能夠接受新的功能來應對改變的需求

 

對擴展開放,對修改關閉?聽起來很矛盾。設計如何兼顧二者?

1、這是一個很好的問題,咋聽之下,的確感到矛盾,畢竟,越難修改的事物,就越難以擴展,不是嗎?

2、可是,有些聰明的OO技巧,容許系統在不修改代碼的狀況下,進行功能擴展。想一想觀察者模式,經過加入新的觀察者,咱們能夠在任什麼時候候擴展Subject(主題),並且不需向主題中添加代碼。之後,你還會陸續看到更多的擴展行爲的其餘OO設計技巧

 

好吧!我瞭解觀察者模式,可是該如何將某件東西設計成能夠擴展,又禁止修改?

許多模式是長期經驗的實證,可經過提供擴展的方法來保護代碼免於被修改。

裝飾者模式就是遵循開放——關閉原則的一個很好的例子

 

我如何讓設計的每一個部分都遵循開放——關閉原則?

一般,你辦不到。要讓OO設計同時具有開放性和關閉性,又不修改現有的代碼,須要花費許多時間和努力。通常來講,咱們實在沒有閒工夫把設計的每一個部分都這麼設計(並且,就算作獲得,也可能只是一種浪費)。遵循開放——關閉原則,一般會引入新的抽象層次,增長代碼的複雜度。你須要把注意力集中在設計中最有可能改變的地方,而後應用開放——關閉原則

 

我怎麼知道,哪些地方的改變是更重要呢?

這牽涉到設計OO系統的經驗,和對你工做領域的瞭解。多看一些其餘的例子能夠幫你學習如何辨別設計中的變化區

 

總結

雖然彷佛有點矛盾,可是的確有些技術能夠容許在不直接修改代碼的狀況下對其進行擴展

在選擇須要被擴展的代碼部分時要當心。每一個地方都採用開放——關閉原則時一種浪費,也不必,還會致使代碼變得複雜且難以理解

 

認識裝飾者模式

1、裝飾者和被裝飾對象有相同的超類型

2、你能夠用一個或多個裝飾者包裝一個對象

3、既然裝飾者和被裝飾對象有相同的超類型,因此在任何須要原始對象(被包裝的)的場合,能夠用裝飾過的對象代替它

4、裝飾者能夠在所委託被裝飾者的行爲以前與/或以後,加上本身的行爲,以達到特定的目的

5、對象能夠在任什麼時候候被裝飾,因此能夠在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象

 

定義裝飾者模式

裝飾者模式——動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案

1、每一個組件均可以單獨使用,或者被裝飾器包起來使用

2ConcreteComponent是咱們將要動態地加上新行爲的對象,它擴展自Component

3、每一個裝飾者都"有一個"(包裝一個)組件,也就是說,裝飾者有一個實例變量以保存某個Component的引用

4、裝飾者功能共同實現的接口(也能夠是抽象類)

5ConcreteDecorator有一個實例變量,能夠記錄所裝飾的事物(裝飾者包着的Component)

6、裝飾者能夠擴展Component的狀態

7、裝飾者能夠加上新的方法。就行爲是經過在舊行爲前面或後面作一些計算來添加的

 

裝飾咱們的飲料

 

哎呀!我有點混淆... ..我原覺得在這個模式中不會使用繼承,而是要利用組合取代繼承

Sue:這話怎麼說?

Mary:看看類圖。CondimentDecorator擴展自Beverage類,這用到了繼承,不是嗎?

Sue:的確是如此,但我認爲,這麼作的重點在於,裝飾者和被裝飾者必須是同樣的類型,也就是有共同的超類,這是至關關鍵的地方。在這裏,咱們利用繼承達到「類型匹配」,而不是利用繼承得到「行爲」

Mary:我知道爲什麼裝飾者須要和被裝飾者(即被包裝的組件)有相同的「接口」,由於裝飾者必須能取代被裝飾者,可是行爲又是從哪裏來的?

Sue:當咱們將裝飾者與組件組合時,就是在加入新的行爲。所獲得的新行爲,並非繼承自超類,而是由組合對象的來的

Mary:好的。繼承Beverage抽象類,是爲了有正確的類型,而不是繼承它的行爲。行爲來自裝飾者和基礎組件,或與其餘裝飾者之間的組合關係

Sue:正是如此

Mary:哦!我明白了。並且由於使用對象組合,能夠把全部飲料和調料更有彈性地加以混合與匹配,很是方便

Sue:是的。若是依賴繼承,那麼類得行爲只能在編譯時靜態決定。換句話說,行若是不是來自超類,就是子類覆蓋後的版本。反之,利用組合,能夠把裝飾者混合着用... ...並且是在"運行時"

Mary:並且,如我所理解的,咱們能夠在任什麼時候候,實現新的裝飾者增長新的行爲。若是依賴繼承,每當須要新行爲時,還的修改現有的代碼

Sue:的確如此

Mary:我還剩下一個問題,若是咱們須要繼承的是component類型,爲何不Beverage類設計成一個接口,而是設計成一個抽象類呢?

Sue:關於這個嘛,還記得嗎?當初咱們從星巴茲拿到這個程序時,Beverage已是一個抽象類了。一般裝飾者模式是採用抽象類,可是在Java中可使用接口。儘管如此,一般咱們都努力避免修改現有的代碼,因此,若是抽象類運做的很好,仍是別去修改它

 

裝飾者的告白

HeadFirst:歡迎裝飾者模式,據說你最近情緒有點差?

裝飾者:是的,我知道你們都認爲我是一個有魅力的設計模式,可是,你知道嗎?我也有本身的困擾,就和你們同樣

HeadFirst:願意讓咱們分擔一些你的困擾嗎?

裝飾者:固然能夠。你知道我有能力爲設計注入彈性,這是毋庸置疑的,可是我也有「黑暗面」。有時候我會在設計中加入大量的小類,這偶爾會致使別人不一樣意瞭解個人設計方式

HeadFirst:你可以舉個例子嗎?

裝飾者:以Java I/O庫來講,人們第一次接觸到這個庫時,每每沒法輕易地理解它。可是若是他們能認識到這些類都是用來包裝InputStream的,一切都會變得簡單多了

HeadFirst:聽起來並不嚴重。你仍是一個很好的模式,只須要一點點的教育,讓你們知道怎麼用,問題就解決了

裝飾者:恐怕不止這些,我還有類型問題。有些時候,人們在客戶代碼中依賴某種特殊類型,而後突然導入裝飾者,卻又沒有周詳地考慮一切。如今,個人一個優勢是,你一般能夠透明地插入裝飾者,客戶程序甚至不須要知道它是在和裝飾者打交道。可是,如我剛剛所說的,有些代碼會依賴特定的類型,而這樣的代碼一導入裝飾者,嘭!出情況了!

HeadFirst:這個嘛,我相信每一個人都必須瞭解到,在插入裝飾者時,必需要當心謹慎。我不認爲這是你的錯!

裝飾者:我知道,我也試着不這麼想。我還有一個問題,就是採用裝飾者在實例化組件時,將增長代碼的複雜度。一旦使用裝飾者模式,不僅須要實例化組件,還要把此組件包裝進裝飾者中,天曉得有幾個

HeadFirst:我下週會訪談工廠(Factory)模式和生成器(Builder)模式,我據說他們對這個問題有很大的幫助

裝飾者:那卻是真的。我應該常和這些傢伙聊聊

HeadFirst:咱們都認爲你是一個好的模式,適合用來創建有彈性的設計,維持開放——關閉原則,你要開心一點,別負面思考

裝飾者:我儘可能把。謝謝你

 

再看OO原則

1、封裝變化

2、多用組合,少用繼承

3、針對接口編程,不針對實現編程

4、爲交互對象之間的鬆耦合設計而努力

5、對擴展開放,對修改關閉

 

裝飾者模式定義

動態地將責任附加到對象上。想要擴展功能,裝飾者提供有別於繼承的另外一種選擇

 

要點

1、繼承屬於擴展形式之一,但不見得是達到彈性設計的最佳方式

2、在咱們的設計中,應該容許行爲能夠被擴展,而無須修改現有的代碼

3、組合和委託可用於在運行時動態地加上新的行爲

4、除了繼承,裝飾者模式也可讓咱們擴展行爲

5、裝飾者模式意味着一羣裝飾者類,這些類用來包裝具體組件

6、裝飾者類反應出被裝飾的組件類型(事實上,他們具備相同的類型,都通過接口或繼承實現)

7、裝飾者能夠在被裝飾者的行爲前面與/後後面加上本身的行爲,甚至將被裝飾者的額行爲整個取代掉,而達到特定的目的

8、你能夠用無數個裝飾者包裝一個組件

9、裝飾者通常對組件的客戶是透明的,除非客戶程序依賴於組件的具體類型

10、裝飾者會致使設計中出現許多小對象,若是過分使用,會讓你的程序變得很複雜

相關文章
相關標籤/搜索