[design-patterns]設計模式之一策略模式


設計模式

從今天開始開啟設計模式專欄,我會系統的分析和總結每一個設計模式以及應用場景。那么首先,什么是設計模式呢,作為一個軟件開發人員,程序人人都會寫,但是寫出一款邏輯清晰,擴展性強,可維護的程序就不是那么容易做到了。現實世界的問題復雜多樣,如何將顯示問題映射到我們編寫的程序中本就是困難重重。另一方面,軟件開發中一個不變的真理就是“一切都在變化之中”,這種變化可能來自於程序本身的復雜度,也可能來自於客戶不斷變化的需求,這就要求我們在編寫程序中一定要考慮變化的因素,將變化的因素抽離出來,設計出一款低耦合的程序就成了程序設計中的難點。

設計模式就是用來解決以上的這些問題的一種方法。應用好設計模式,可以讓我們免受程序設計中的變化的苦惱。從設計初至今,設計模式不是一成不變的,也並不是庫,它比庫更加高級,而且並不是用來解決某一種特定的問題,而是廣泛的面向我們在程序開發中遇到的困境。在我們學習完一門語言的相關語法后,我相信設計模式應該作為緊接着學習的重點,它會讓你對相關語法的認識更加深刻。比如說java中的接口就是一例,接口是讓java流行的重要原因,但是初學完接口的使用,我們並不能很好的掌握接口,只有應用好設計模式,接口才能夠大放異彩,我們也才能夠真正的體會到接口給面向對象設計帶來的魔力。實際上,在很多流行的開源庫或者框架中,我們可以看到大量的使用設計模式的例子。

在網絡中,有關於設計模式的講解不在少數,在這里我並不會引出復雜的定義,而是以例子作為基礎,道出我們在軟件開發中面臨的問題,然后引出設計模式,看看設計模式是如何幫助我們解決這些問題的。在之后的講解中,我都會以java為主要語言,因為它的語法足夠簡單,使用其他語言作為主語言的開發者觸類旁通即可。

最后,關於學習設計模式,希望大家能看一些經典的書籍,不要以其他網路上的博客(包括我的)做抓手學習。畢竟別人嚼過的不香,只有自己品味才能真正理解。書籍最經典的莫過於四人組(經常簡稱為GoF,即Group of Four)的《設計模式:可復用面向對象軟件的基礎》,初學可以看《Head First設計模式》,另有一本《大話設計模式》是國人寫的評價也很高,同樣適合初學者。

啰嗦了這么多,接下來開始步入正題。

設計模式之一策略模式

現在的你,在一家游戲開發公司供職。某一天,你的上司讓你開發出一套簡單的游戲,游戲中有多個角色,每個角色可以使用武器進行攻擊。你打算先以角色入手,很快你設計出了以下的模型:

 

但是很快,你就發現這樣設計並沒有一開始想象的那么美好,因為你的上司讓你給角色新增一個聊天功能,這時候問題出現了。

你很快想到因為所有角色都繼承於Role,所以給Role添加一個聊天方法是理所應當的:

但是這個時候,你的同事告訴你不要這樣做,因為他之前設計了一些怪物模型,同樣繼承於Role類,很明顯,怪物也可以使用武器戰斗,但是他們不能參與聊天。

你很郁悶,你的同事繼承了你的類卻沒有告訴你,不過你告訴他不用大驚小怪,只要在怪物中重寫聊天方法,讓他們什么都不做就好了:

但是你的同事告訴你不要逗他,因為他寫的怪物少說已經有了二十幾個,你不能殘忍的讓你的同事挨個去重寫chat方法。

當你們正在商討解決方案時,你的上司告訴你,現在角色的武器太單一了,也就是說你不能把所有的戰斗實現都放在父類中,我們需要更加多樣化的武器。

你很苦惱,但是作為有經驗的開發人員,你很快想到可以采用接口去實現,將所有的動作都抽象成一個個接口,讓子類實現需要的接口,這樣可以解決上司的需求和同事的問題:

但是,這一次是你自己將自己否定了,因為你的角色設計已經進行了很多,你不想將每個角色的方法都重新實現一遍,而且,這些武器很多都是重復的,作為受過良好的OO設計課程的開發人員,你不會強迫自己去寫這些重復的代碼。最重要的是,你的開發經驗告訴你,這些武器很可能會發生改變,如果這樣設計,當發生變化時,你就不得不去檢查每一個角色類,並修改其中的代碼。同樣的,你的同事也同意你的看法。

設計原則

現在,你迫切的希望有一種設計模式能夠解救你於水火之中。不過在揭曉這個設計模式之前,我們先回到原點,看一下我們遇到的問題到底是由什么原因造成的。

我們可以看到,繼承不能很好的解決我們的問題,因為角色的行為是不斷發生變化的,他們使用不同的武器和技能,並且你的上司很可能會不斷地要求你添加新的行為。接口看起來不錯,但是接口不具有實現代碼,無法做到代碼的復用,這意味着如果你想將某一種行為做統一的變化,就需要一個一個類去檢查方法的實現。

要解決這個問題,首先我們需要明確一些設計原則,掌握了這些設計原則,我們才能更好的運用設計模式,解決問題。

面對以上的情況,我們有一個原則正好適用,那就是“封裝變化”:

找出應用中可能需要變化的地方,把它們獨立出來,不要和那些固定的代碼混在一起。

那么對於當前的應用來講,戰斗以及聊天功能都是變化的功能,我們就應該將他們獨立出來,分別設計戰斗功能和聊天功能。接下來就是如何實現這兩個功能了。這又會用到一個設計原則,那就是“接口編程”:

針對接口編程,而不是針對實現編程。

相信這個原則大家都非常清楚,我就不多講了,接下來直接上代碼,這里我只分析戰斗功能,其他功能也是類似的:

設計實現

首先是接口設計:

1 public interface IFight {
2     void fight();
3 }

這個接口很簡單,只是提供了fight方法,接下來我們實現幾個用各種武器戰斗的類:

 1 public class FightUseAxe implements IFight {
 2     @Override
 3     public void fight() {
 4         System.out.println("使用斧子戰斗");
 5     }
 6 }
 7 ===============================================
 8 public class FightUseBlade implements IFight {
 9     @Override
10     public void fight() {
11         System.out.println("使用劍戰斗");
12     }
13 }
14 ===============================================
15 public class FightUseKnife implements IFight {
16     @Override
17     public void fight() {
18         System.out.println("使用匕首戰斗");
19     }
20 }

注意上面的代碼不能寫在一個同一個文件中。

接下來我們對角色類進行重構:

 1 public abstract class Role {
 2     
 3     private IFight weapon;
 4     
 5     public void fight() {
 6         weapon.fight();
 7     }
 8     
 9     public void setWeapon(IFight weapon) {
10         this.weapon = weapon;
11     }
12     
13     public abstract void display();
14 }

父類中我們實現了fight()方法,做到了統一管理,但是具體方法的實現實際上是交給子類去完成了,也就是IFight屬性的賦值是在子類中完成的。接下來我們來看其中一個子類:

 1 public class King extends Role {
 2     @Override
 3     public void display() {
 4         System.out.println("顯示國王的樣子");
 5     }
 6     
 7     public static void main(String[] args) {
 8         Role role = new King();
 9         role.display();
10         role.setWeapon(new FightUseAxe());
11         role.fight();
12         role.setWeapon(new FightUseBlade());
13         role.fight();
14     }
15     /**
16      * 運行結果:
17      * 顯示國王的樣子
18      * 使用斧子戰斗
19      * 使用劍戰斗
20      */
21 }

我們可以看到,通過將戰斗獨立出來,我們實現了使用者(King)和武器的松耦合,即我們可以動態的改變角色使用的武器。同樣的對於其他的角色也是一樣,實際上,這是一種方法的委托,角色類不再實現戰斗的具體方式,而是交給了成員屬性IFight去執行。從另一方面來說,這是一種組合的概念,它和繼承不同的地方在於,角色的戰斗方式並不是繼承而來的,而是和適當的戰斗類來組合而成。在這個實例中,我們可以明顯的看到組合的優勢明顯大於繼承。

同樣,這是一個很重要的設計原則,“多用組合,少用繼承”。

其實,從更加抽象的角度來講,例子中的戰斗方式實際上是一種算法,通過策略模式,我們分離了使用算法的角色和算法之間的聯系。因此我們可以給出策略模式的定義:

策略模式定義了算法族,分別封裝起來,讓他們之間可以相互替換,此模式讓算法的變化獨立於使用算法的客戶。

總結

今天我們學習了第一個設計模式即策略模式,當我們面對一些程序中可能會頻繁變化的部分時可以采用此模式,它讓算法的實現獨立於使用算法的客戶。同時我們還學習了三個面向對象設計中的重要原則:

  • 封裝變化
  • 多用組合,少用繼承
  • 針對接口編程,而不針對實現編程

最后,為了鞏固我們的學習成果,下面我們思考這樣一個問題。在一片草原中,一群游牧民族養了一群羊和一群馬,也就是說在這個草原中有三種生物,分別是人、馬、羊、他們的動作有吃(eat)和睡(sleep),不過吃和睡的方式不同,馬是站着吃站着睡,羊是站着吃趴着睡,人是坐着吃躺着睡。那么我們該怎樣設計該OO模型,在實現功能的基礎上,達到低耦合的要求,滿足不斷變化的需求呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM