開發一款游戲,裡面有各種鴨子,這些鴨子有共同點:會游泳、會叫; 1.設計超類Duck,裡面有swim()方法和quack()方法,所有鴨子繼承此超類,那麼繼承的對象便都有了游泳和叫的技能; 2.需求變更:增加三種叫的方法,不同的鴨子叫聲不同,有“吱吱叫”、“呱呱叫”,還有不會叫;那麼可以覆寫每個子類 ...
開發一款游戲,裡面有各種鴨子,這些鴨子有共同點:會游泳、會叫;
1.設計超類Duck,裡面有swim()方法和quack()方法,所有鴨子繼承此超類,那麼繼承的對象便都有了游泳和叫的技能;
1 public abstract class Duck { 2 /*所有的鴨子都會游泳*/ 3 public void swim() { 4 System.out.println("I can swim"); 5 } 6 /*所有的鴨子都會叫*/ 7 public void quack() { 8 System.out.println("I can quack"); 9 } 10 /*其它行為*/ 11 public abstract void display(); 12 }
2.需求變更:增加三種叫的方法,不同的鴨子叫聲不同,有“吱吱叫”、“呱呱叫”,還有不會叫;那麼可以覆寫每個子類quack()方法,使鴨子叫聲不同。
3.需求變更:鴨子得會飛;那麼可以為超類Duck增加fly()方法,所有繼承的子類便有了飛的技能;
4.需求變更:有些鴨子不會飛;那麼覆寫每個鴨子的fly()方法,使不會飛的鴨子不能飛;
這裡已經存在一些問題:每當有新的鴨子出現,都需要檢查fly()方法和quack()方法,確認是否需要覆寫。這種繼承 and 覆寫的方法很糟糕。
改變:
繼承或許不可取,改用介面;可以把fly和quack提取為兩個介面,會飛的實現flyable介面,會叫的實現quackable介面;
改用介面帶來的麻煩:每個實現flyable或quackable的子類,都需要去覆寫fly()方法和quack()方法,代碼根本不能復用,導致做很多重覆工作。
問題分析:鴨子的fly和quack的行為在子類中不斷的變化,讓子類都具有這些行為是不恰當的;使用flyable和quackable介面,雖然可以解決一部分問題,但是fly()和quack()方法需在每個子類中覆寫,代碼不能復用,會增加很多重覆工作。
有種設計原則很適合這樣的情況:
一、找出需求中可能變化之處,把它們獨立出來,不和不變的代碼混在一起;(可以把易於變化的代碼封裝起來,以便可以輕易的改動和擴充此部分,而不影響不需要改動的其它部分)。
二、針對介面編程而非針對實現。
三、多用組合,少用繼承。
開始將fly行為和quack行為獨立出來:
準備組建兩組類(完全脫離Duck),一組是fly相關的(實現FlyBehavior介面),一組是quack相關的(實現QuackBehavior介面)。有:會飛(FlyWithWings)、不會飛(FlyNoWay)、呱呱叫(Quack)、吱吱叫(Squeak)、安靜(MuteQuack)等類;
1 /*會飛*/ 2 public class FlyWithWings implements FlyBehavior { 3 4 public void fly() { 5 System.out.println("I'm flying."); 6 } 7 8 } 9 /*不會飛*/ 10 public class FlyNoWay implements FlyBehavior { 11 12 public void fly() { 13 System.out.println("I can't fly."); 14 } 15 16 } 17 /*呱呱叫*/ 18 public class Quack implements QuackBehavior { 19 20 public void quack() { 21 System.out.println("呱!呱!"); 22 } 23 24 } 25 /*吱吱叫*/ 26 public class Squeak implements QuackBehavior { 27 28 public void quack() { 29 System.out.println("吱!吱!"); 30 } 31 32 } 33 /*不會叫*/ 34 public class MuteQuack implements QuackBehavior { 35 36 public void quack() { 37 System.out.println("<< Silence >>"); 38 } 39 40 }
新設計中,鴨子的子類將使用介面(FlyBehavior和QuackBehavior)所表示的行為,子類不需要去實現特定的行為(之前的設計,每個子類需要實現不同的功能覆寫,把子類與實現綁定),而是由FlyBehavior和QuackBehavior的實現類來實現特定的行為。
整合鴨子的行為:
1 public abstract class Duck { 2 3 public FlyBehavior flyBehavior; 4 public QuackBehavior quackBehavior; 5 6 //可以動態的設定既成的行為 7 public void setFlyBehavior(FlyBehavior flyBehavior) { 8 this.flyBehavior = flyBehavior; 9 } 10 public void setQuackBehavior(QuackBehavior quackBehavior) { 11 this.quackBehavior = quackBehavior; 12 } 13 14 /** 15 * 其它行為 16 */ 17 public abstract void display(); 18 19 /** 20 * 能夠表演飛和叫的行為 21 */ 22 public void performFly() { 23 flyBehavior.fly(); 24 } 25 public void performQuack() { 26 quackBehavior.quack(); 27 } 28 29 /** 30 * 所有鴨子都會游泳 31 */ 32 public void swim() { 33 System.out.println("I can swim."); 34 } 35 }
鴨子對象不親自處理quack和fly的行為,而是由QuackBehavior和FlyBehavior介面的對象去處理。並且子類也可動態設定飛和叫的行為。
子類鴨子(綠頭鴨)代碼實例:
1 /** 2 * 綠頭鴨 3 * 4 */ 5 public class MallardDuck extends Duck { 6 7 public MallardDuck() { 8 flyBehavior = new FlyWithWings(); 9 quackBehavior = new Quack(); 10 } 11 12 @Override 13 public void display() { 14 System.out.println("I'm a real mallard duck!"); 15 } 16 17 }
測試代碼的行為:
1 public class DuckTest { 2 3 @Test 4 public void test() { 5 Duck mallard = new MallardDuck(); 6 mallard.swim(); 7 mallard.display(); 8 //行為沒有被改變之前 9 mallard.performQuack(); 10 mallard.performFly(); 11 //行為改變之後 12 mallard.setQuackBehavior(new Squeak()); 13 mallard.setFlyBehavior(new FlyNoWay()); 14 mallard.performQuack(); 15 mallard.performFly(); 16 17 } 18 }
運行結果:
I can swim.
I'm a real mallard duck!
呱!呱!
I'm flying.
吱!吱!
I can't fly.
本例中將鴨子與兩種行為QuackBehavior和FlyBehavior介面類組合使用,彈性大,且易於維護。