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