设计模式_第二篇_策略模式
本文是我通過學(xué)習(xí)《Head First 設(shè)計(jì)模式》而寫。
?
作為我要描述的第一個(gè)模式,首先要說什么是設(shè)計(jì)模式,然后,用一個(gè)實(shí)例,并對(duì)這個(gè)實(shí)例不斷的改進(jìn),引出策略模式。
?
與其空泛地給出一堆描述,倒不如給出通過一個(gè)實(shí)例、一個(gè)情景,來引出你要說的東西。因?yàn)?#xff0c;人們對(duì)于事物的理解,越是具體、形象,就越容易,而但凡理論性、抽象性的東西,你無論怎樣描述它,也只是用一個(gè)概念去解釋另一個(gè)概念。對(duì)于一個(gè)沒有多少項(xiàng)目經(jīng)驗(yàn)的人來說,著實(shí)不易?!禜ead First設(shè)計(jì)模式》這本書做得就很好。
?
上面這句話,有三個(gè)關(guān)鍵詞:情境、問題和解決方案。所謂“情境”是應(yīng)用某個(gè)設(shè)計(jì)模式的情況;所謂“問題”是你在某個(gè)情境下達(dá)到的目標(biāo),但也可以是某個(gè)情境下的約束;而“解決方案”是你所追求的,一個(gè)通用的設(shè)計(jì),用來解決約束,達(dá)到目標(biāo)。
?
需要明確幾點(diǎn):
- 項(xiàng)目不一定非要使用設(shè)計(jì)模式。使用設(shè)計(jì)模式只是為了讓程序更加靈活、更容易維護(hù),尤其是當(dāng)需求變化的時(shí)候。但模式會(huì)無形中增加程序的復(fù)雜性,這是我們所不期望的。我們期望,用簡(jiǎn)單、清晰的方法來解決復(fù)雜的問題,使程序更容易讓人理解,而不是“機(jī)器”——簡(jiǎn)單的想1一樣。任何一個(gè)程序員都能寫出讓機(jī)器理解的代碼,但只有一個(gè)熟練的程序員才能寫出讓絕大多數(shù)人都能理解的程序。
- 設(shè)計(jì)模式初學(xué)者的誤區(qū),包括我自己,總是希望為自己的程序找到一個(gè)模式(雖然這個(gè)初衷是想練習(xí)這些模式)。有一句經(jīng)典的話:“我要為 'Hello World' 找一個(gè)設(shè)計(jì)模式”。
- 你可以不會(huì)使用設(shè)計(jì)模式,但你絕對(duì)不能不知道它的存在。
- 模式本身是不存在的,它只是在長期的項(xiàng)目實(shí)踐中的一種經(jīng)驗(yàn)。你可以有自己的模式,并發(fā)布出去。但你的模式必須有三次成功的案例,并經(jīng)過其他人的評(píng)價(jià)后才可能被列入模式目錄。
?
下面,通過一個(gè)實(shí)例,來說明策略模式。
假如,你所在公司要求你制作一個(gè)模擬各種鴨子飛行的程序。你立刻就會(huì)想到,所有的鴨子都會(huì)游泳、都會(huì)叫,那么,可以設(shè)計(jì)一個(gè)鴨子的抽象類(Duck),然后讓所有的鴨子,如綠頭鴨(MallardDuck)或紅頭鴨(RedHeadDuck)都繼承這個(gè)抽象類。如圖1所示:
其中,
- Duck 類是一個(gè)抽象類。MallardDuck 類和 RedHeadDuck 繼承 Duck 類。
- 所有的鴨子都會(huì)游泳、都會(huì)叫,因此,由 Duck 類來實(shí)現(xiàn) quack() 和 swim() 方法。
- 每個(gè)鴨子都有自己的 display() 方法,因此,Duck 類中的 display() 是抽象abstract方法。
這個(gè)設(shè)計(jì)看似不錯(cuò),但有什么缺點(diǎn)?現(xiàn)在,公司要求——鴨子要能飛。你很容易想到,只要在 Duck 類中,加入并實(shí)現(xiàn) fly() 方法,那么,Duck 類的子類都可以繼承這個(gè)方法。如圖2所示:
但問題也來了——不會(huì)飛的橡皮鴨 RubberDuck 到處飛。因此,在抽象類 Duck 中加入 fly() 方法后,其所有子類也就都具備了 fly() 方法,連不該具備 fly() 方法的子類也無法避免。解決的方法也很容易想到——覆蓋子類的 fly() 方法。
但這樣做也有問題啊!以后,要是再加入誘餌鴨 DecoyDuck?誘餌鴨即不會(huì)飛,也不會(huì)叫。這樣,除了要覆蓋 fly() 方法,還要覆蓋 quack() 方法。一個(gè)公司的產(chǎn)品都會(huì)定期更新,加入其他種類的鴨子。這樣,你不得不每次都要檢查 fly() 和 quack() 方法。因此,這也不是一個(gè)好的解決方案。
采用接口總可以了吧!如圖3所示:
其中,
- Duck 類仍然是個(gè)抽象類。所有種類的鴨子,如綠頭鴨(MallardDuck)、紅頭鴨(RadHeadDuck)、橡皮鴨(RubberDuck)和誘餌鴨(DecoyDuck),都要繼承 Duck 類。
- Duck 類的子類必須繼承 Flyable 和 Quackable 接口,并實(shí)現(xiàn) fly() 和 quack() 方法。
對(duì)于這個(gè)設(shè)計(jì),雖然不會(huì)出現(xiàn),橡皮鴨(RubberDuck)到處飛的情況,但代碼無法復(fù)用。因?yàn)?#xff0c;顯然綠頭鴨和紅頭鴨都會(huì)飛都會(huì)叫,即它們的 fly() 和 quack() 方法的實(shí)現(xiàn)一樣,而橡皮鴨和誘餌鴨都不會(huì)飛,即 fly() 方法的實(shí)現(xiàn)一樣等等——這只是從一個(gè)“惡夢(mèng)”跳到另一個(gè)“惡夢(mèng)”而已。
那么究竟應(yīng)該怎么做?答案是:找出應(yīng)用中可能需要變化的地方,把它們獨(dú)立出來,不要和那些不需要變化的代碼混在一起。在本情景中,將鴨子的“飛”和“叫”的行為從抽象類 Duck 中分離出來。如圖4所示:
其中,
- Duck 類還是一個(gè)抽象類。FlyBehavior 和 QuackBehavior 接口聚合到 Duck 類,作為 Duck 類的屬性。
- ModelDuck 類繼承 Duck 類。該類重構(gòu)的 display() 方法是所有鴨子都具備的共同需求。因?yàn)?#xff0c;無論什么種類的鴨子,最終都是要顯示出來,或是一邊飛,一邊叫;或是不飛(也許會(huì)飛,也許不會(huì)飛),只叫;或是一邊游泳,一邊叫……等等。我在開發(fā)時(shí),一般將像 ModelDuck 這樣的類,命名為 BaseDuck。
- 類 FlyNoWay、FlyWithWings、FlyRocketPowered 分別繼承接口 FlyBehavior,實(shí)現(xiàn)里邊的方法——“不會(huì)飛”、“用翅膀飛”和“坐火箭飛”。繼承 QuackBehavior 接口的三個(gè)類同理。
這個(gè)設(shè)計(jì)究竟好在哪里?我覺得有如下幾點(diǎn):
- 將鴨子“飛”和“叫”的行為(方法)從抽象類Duck中分離出來,這樣,Duck類以及其子類就不需要再知道“飛”和“叫”是如何實(shí)現(xiàn)的,有利于加入新的鴨子子類,而完全不會(huì)影響現(xiàn)有的代碼;
- 當(dāng)需要添加新的鴨子“飛”的行為或是新的“叫”的行為時(shí),只要繼承相應(yīng)的接口即可,完全不會(huì)影響現(xiàn)有的代碼;
- 為了使程序能在運(yùn)行時(shí)改變鴨子“飛行”和“叫”的狀態(tài),讓程序更加靈活,在Duck類中添加兩個(gè)set方法和perform方法,分別設(shè)置鴨子“飛行”和鴨子“叫”的狀態(tài),然后再讓perform方法執(zhí)行這些鴨子的行為;
- 另外,通常情況下,在實(shí)際的項(xiàng)目中,當(dāng)我們需要添加新類型的鴨子時(shí),不會(huì)直接繼承Duck,而是用一個(gè)基類先繼承這個(gè)抽象類,比如用ModleDuck類繼承Duck類,再讓新的鴨子類繼承這個(gè)基類,這樣,會(huì)使程序變得更加靈活。
?
從以上在對(duì)策略模式的分析中,可以得到如下經(jīng)驗(yàn)和結(jié)論:
- 如果為了代碼復(fù)用而使用繼承,結(jié)局往往并不完美;
- 針對(duì)接口編程;
- 將程序變化的部分和不變化的部分分離。
所謂策略模式,就是它定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶。
轉(zhuǎn)載于:https://www.cnblogs.com/liuning8023/archive/2011/08/25/2153738.html
總結(jié)
以上是生活随笔為你收集整理的设计模式_第二篇_策略模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。