設想有一個游戲,游戲中有各種鴨子,它們可以飛,也可以呱呱叫。這樣一個游戲該怎樣設計呢?一、使用繼承:鴨子雖然有不同的種類,但是也有一定的相同之處,所以我們可以從鴨子中提取一個父類Duck,讓不同種類的鴨子類繼承自父類,將所有鴨子共有的屬性和行為放到父類中。這樣做看似沒有錯誤,但實際上存在以下兩個方面...
設想有一個游戲,游戲中有各種鴨子,它們可以飛,也可以呱呱叫。這樣一個游戲該怎樣設計呢?
一、使用繼承:
鴨子雖然有不同的種類,但是也有一定的相同之處,所以我們可以從鴨子中提取一個父類Duck,讓不同種類的鴨子類繼承自父類,將所有鴨子共有的屬性和行為放到父類中。這種思想對應的類圖如下:
這樣做看似沒有錯誤,但實際上存在以下兩個方面的錯誤:
1、子類的可用行為可能遠大於我們的期待範圍。把所有方法和屬性都定義在Duck父類中,再由所有子類去繼承這個父類,那麼所有的子類就都具備父類的方法和屬性。就拿這個游戲的例子來說,如果加入一個方法fly(),那麼就算是橡皮鴨(RubberDuck)類也一定會具備fly()的行為。
2、這種方法並不能有效的解決代碼重覆問題。拿上一條中的fly()方法來說,既然不同的鴨子具有不同的飛行方式,則fly()方法的方法體一定是寫在子類中;而不同種類的鴨子的飛行方式也可能是相同的,所以我們就可能不得不在多個鴨子中使用相同的fly()方法,而在另外一些鴨子中使用另一種fly()方法。
二、增加介面:
我們可以把Duck父類中的fly()方法提取出來定義成介面,稱為Flyable,其中定義一個抽象方法fly(),讓可以飛的Duck子類實現這個介面並實現fly()方法。各個類中的行為細節及類間關係如下圖所示。
使用介面有效的解決了上面的第一個問題,即可以有效的控制各個子類的行為範圍,不會出現“子類具有父類的所有方法”的情況了。但是,JAVA中的介面中不具備代碼實現功能,所以這樣做並沒有解決代碼重覆的問題。
三、解決問題:
我們需要找出應用中可能需要變化的地方,把它們獨立出來,不要和那些不需要變化的代碼混在一起,即把會變化的部分取出並封裝起來,一邊以後可以輕易的改動或擴展此部分,而不影響不需要變化的部分。
在上面的兩次嘗試(使用繼承、增加介面)中,我們都只是把共有的屬性和行為抽出來封裝成父類或介面,但在這裡,我們要儘量兩次封裝,即在業務類上面還有兩層封裝類。對於一種行為,我們首先將這種行為抽取出來作為一個總的行為介面,然後再在這個介面下麵定義多個不同的實現類,最後再在業務類中調用父類完成業務。簡單的說,在上面兩種嘗試中,我們只是定義了兩個互不相關的行為,而在這種方法中,我們定義的是一組有關聯的行為。
拿我們這個例子中的fly()方法舉例,飛,可能是用翅膀飛(FlyWithWings),也可能是坐火箭飛(FlyRocketPower)。因此,我們定義一個總的介面FlyBehavior,在這個介面中定義一個抽象方法fly()。再定義兩個實現FlyBehavior介面的類FlyWithWings和FlyRocketPower,這兩個類實現FlyBehavior介面,並實現介面中的fly()方法。另外,我們在Duck父類中定義一個FlyBehavior的介面變數,在生成鴨子實體的時候,給其FlyBehavior賦值,以此決定鴨子的飛行方式。我們還可以在Duck父類中定義一個設置飛行方式的方法setFlyBehavior(),來動態的改變鴨子的飛行方式。以下是類圖:
以下貼出對這個問題的解決方案的代碼:
介面FlyBehavior中的代碼:
1 public interface FlyBehavior { 2 void fly(); 3 }
FlyBehavior介面的實現類FlyWithWings(用翅膀飛)類中的代碼:
1 public class FlyWithWings implements FlyBehavior { 2 public void fly() { 3 System.out.println("我用翅膀飛!"); 4 } 5 }
FlyBehavior介面的實現類FlyRocketPower(坐火箭飛)類中的代碼:
1 public class FlyRocketPower implements FlyBehavior { 2 public void fly() { 3 System.out.println("我坐火箭飛!"); 4 } 5 }
FlyBehavior介面的實現類FlyNoWay(不會飛)類中的代碼:
1 public class FlyNoWay implements FlyBehavior { 2 public void fly() { 3 System.out.println("我不會飛!"); 4 } 5 }
Duck父類中的代碼:
1 public abstract class Duck { 2 private FlyBehavior flyBehavior; // 飛行行為介面對象 3 // 設置鴨子的飛行行為方式 4 public void setFlyBehavior(FlyBehavior flyBehavior) { 5 this.flyBehavior = flyBehavior; 6 } 7 // 讓鴨子飛行 8 public void performFly() { 9 this.flyBehavior.fly(); 10 } 11 // 輸出鴨子的樣子 12 public abstract void display(); 13 }
Duck的實體類RedHeadDuck(紅頭鴨)類中的代碼:
1 public class RedHeadDuck extends Duck { 2 public RedHeadDuck() { 3 this.display(); 4 super.setFlyBehavior(new FlyWithWings()); 5 } 6 7 public void display() { 8 System.out.println("我是紅頭鴨!"); 9 } 10 }
Duck的實體類RubberDuck(橡皮鴨)類中的代碼:
1 public class RubberDuck extends Duck { 2 public RubberDuck() { 3 this.display(); 4 super.setFlyBehavior(new FlyRocketPower()); 5 } 6 7 public void display() { 8 System.out.println("我是橡皮鴨!"); 9 } 10 }
主函數Main中的代碼:
1 public class Main { 2 public static void main(String args[]) { 3 // 第一隻鴨子:紅頭鴨 4 Duck duck1 = new RedHeadDuck(); 5 duck1.performFly(); 6 // 第二只鴨子:橡皮鴨(由坐火箭飛改變為不會飛) 7 Duck duck2 = new RubberDuck(); 8 duck2.performFly(); 9 duck2.setFlyBehavior(new FlyNoWay()); 10 duck2.performFly(); 11 } 12 }
四、總結:
本例主要用了策略模式。所謂策略模式,即定義演算法族(幾組行為),每組行為封裝一種行為的不同表現形式,讓他們之間可以互相替換。這種模式讓演算法的變化獨立於使用演算法的客戶。