《Head First 設計模式》,好書!
策略模式經典的例子:鴨子模型。
假設我們主要研究鴨子(DUCK)的"叫(quack)"和鴨子的飛(fly)。
public abstract class Duck{ //呱呱叫 quack(); //飛 fly(); }
1.假如,我們想要很多種鴨子在屏幕上飛和叫,想到了設計一個DUCK父類,讓許許多多的鴨子子類繼承fly()和quack()。這樣鴨子們就可以自由的在屏幕飛和叫了。
2.問題來了,屏幕上有個RubberDuck(橡皮鴨)在飛和呱呱叫,這怎麼行!RubberDuck不會飛並且叫聲是"吱吱"而不是"呱呱"。於是靈機一動,把RubberDuck
的fly()和quack()重寫,覆蓋父類方法。屏幕看上去舒服多了~
3.屏幕上怎麼又多出來了一個DecoyDuck(誘餌鴨,木頭制作),再重寫父類方法,讓它不會飛也不會叫。
4.....不一會就感到厭煩了,老是重寫了父類方法怎麼行。
直接繼承有太多的缺點:
於是,我們要改變設計方法。
這些行為(fly和quack)繼承起來太難控制,用接口把。嗯~把它們從DUCK父類中提取出來,分別定義一個接口。
設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混合在一起。
鴨子發出的聲音不一樣,可以"呱呱Quack"、"吱吱Squack"、"無聲MuteQuack",分別實現fly的接口(FlyBehavior)
這樣,鴨子發出quack的時候就可以調用特定的方法。
因為分離出來,這些行為和鴨子完全無關。實現青蛙的時候也可以拿走"呱呱叫"的方法
設計原則:針對接口編程,不針對實現編程。
/* Dog繼承Animal */ //針對實現編程 Dog d = new Dog; d.bark(); //針對接口編程,這裡就是多態。 Animal animal = new Dog(); animal.bark(); //用animal調用bark()方法,至於animal是什麼,我們並不關心。
那麼還有一個問題,每個鴨子都去實現特定的行為方法(如:Quack),這些行為是固定的。可是我想在運行時動態的改變鴨子的行為怎麼辦?
比如,DecoyDuck突然可以飛了。
貌似可以用多態,針對接口編程。"叫"定義了接口QuackBehavior,"飛"定義了接口FlyBehavior。
然後Quack(呱呱叫),SQuack(吱吱叫),MuteQuack(無聲)實現了QuackBehavior。
同理,FlyBehavior也有具體實現。
那麼,父類Duck:
public abstract class Duck{ //聲明QuackBehavior的接口 QuackBehavior quackBehavior; //鴨子對象不直接執行叫的行為,委托給quackBehavior對象。 public void performQuack(){ quackBehavior.quack(); } //可以動態的修改行為,用quackBehavior來什麼行為要什麼行為。
//比如傳入MuteQuack,然後在調用performQuack,是不是就相當於 MuteQuack.quack(),不發聲了。
public void setQuackBehavior(QuackBehavior qb){
quackBehavior = qb;
} }
有一個performQuack()方法,這個方法代替被刪除的quack方法(直接繼承的quack方法,在最上面)。
作用就是去使用接口對象 quackBehavior調用quack方法。(或許畫上類圖更清楚)
//叫的接口 public interface QuackBehacior{ public void quack(); } ------------------------------------具體實現-------------------------------------- public class Quack implements QuackBehavior{ public void quack(){ System.out.println("我會呱呱叫"); } } ---------- ------------ public class Squack implements QuackBehavior{ public void quack(){ System.out.println("我會吱吱叫"); } } ---------- ------------ public class MuteQuack implements QuackBehavior{ public void quack(){ System.out.println("我不會發聲"); } }
像不像上面anmial和dog的調用,不需要知道具體是誰,委托給接口對象(可以這樣理解吧)。
那麼,開始了
寫一個鴨子類繼承Duck
public MallardDuck extends Duck{ public MallardDuck(){ //quackBehavior是繼承下來的 quackBehavior = new Quack(); } }
開始測試:
public static void main(String [] args){ Duck mallard = new MallardDuck(); mallard.performQuack(); //輸出呱呱叫 mallard.setQuackBehavior(new MuteQuack()); mallard.performQuack();//輸出無聲 } /* 可能你還有些迷糊,那逐一解釋代碼 1.Duck mallard = new MallardDuck() 執行: quackBehavior = new Quack()//就像實例化了quackBehavior 2.mallard.peiformQuack(); 執行: quackBehavior.quack();//該對象調用Quack類裡的quack()方法 3.mallard.setQuackBehavior(new MuteQuack()); quackBehavior = qb; //qb是不是 new MuteQuack(); 是不是所有的都是圍繞QuackBehavior 接口,針對接口編程。 */
設計原則:多用組合(composition),少用繼承
上文中的fly(實現方式和quack相同)、quack行為就是組合。鴨子的行為不是繼承來的,而是多個行為 組合 來的。
這些行為完全可以被青蛙(呱呱),麻雀(飛)使用,提升了代碼的復用程度。
把"行為"抽象一下,不再叫做行為而是算法族。然後把他們封裝起來(嗯,封裝),可以動態的相互替換(鴨子的行為動態替換)。並且這些算法和要使用這些算法的鴨子沒有任何關系。
總結:
策略模式定義了算法族,分別封裝起來,讓他們之間可以相互替換,此模式讓算法的變化獨立於使用算法的客戶。
這樣看起來是不是很高大上~