Java设计模式(1 / 23):策略模式
定義
策略(Strategy)模式定義了算法族,分別封裝起來,讓它們之間可以互相替換 ,此模式讓算法的變化獨立于使用算法的客戶。
案例:模擬鴨子應用
一開始
新需求:模擬程序需要會飛的鴨子
在父類新添加fly()方法。
這樣做的弊端:并非Duck所有的子類都會飛,如橡皮鴨。
當涉及維護時,為了復用(reuse)目的而使用繼承,結局并不完美。
一種補救的方法把橡皮鴨類中的fly()方法覆蓋掉。
新麻煩:加入誘餌鴨(DecoyDuck)類,它是假鴨,不會飛也不會叫。
利用繼承來提供Duck的行為,這會導致下列缺點:
每當有新的鴨子子類出現,就要被迫檢查并可能需要覆蓋fly()和quark()這簡直是無窮盡的惡夢。
一種改進:可以把fly()取出來,放進一個Flyable接口中。這么一來,只有會飛的鴨子才實現此接口。同樣的方式 ,也可以用來設計一個Quackable接口,因為不是所有的鴨子都會叫。
雖然Flyable與Quackable可以解決一部分的問題(不會再有會飛的橡皮鴨 ),但是卻造成代碼無法復用,這只能算是從一個惡夢跳進另一個惡夢。甚至,在會飛的鴨子中,飛行的動作可能還有多種變化…
這意味著:無論何時你需要修改某個行為,你必須得往下追蹤并修改每一個定義此行為的類,一不小心,可能造成新的錯誤。
(MyNote:Java8的default關鍵可在接口類具有實現代碼。)
美好的愿景:如果能有一種建立軟件的方法,好讓我們需要改變軟件時,可以在對既有的代碼影響最小的情況下,輕易達到花較少時間重做代碼,而多讓程序去做更酷的事。該有多好…
軟件開發的一個不變真理:變化永存
不管當初軟件設計得多好,一陣子之后,總是需要成長與改變,否則軟件就會死亡。
驅動改變的因素很多如:
- 顧客或用戶需要別的東西,或者想要新功能。
- 公司決定采用別的數據庫產品,也從另一家廠商買了數據,這造成數據格式不兼容。
設計原則:變定分離
找出應用中可能需要變化之處,把它們獨立出來 ,不要和那些不需要變化的代碼混在一起。
把會變化的部分取出并封裝起來,好讓其他部分不會受到影響。代碼變化之后,出其不意的部分變得很少,系統變得更有彈性。
(MyNote:把精力集中在變化上。)
換句話說,如果每次新的需求一來,都會變化到某方面的代碼,那么你就可以確定,這部分的代碼需要被抽出來,和其他聞風不動的代碼有所區隔。
下面是這個原則的另一種思考方式:把會變化的部分取出并封裝起來,以便以后可以輕易地擴充此部分,而不影響不需要變化的其他部分。
這樣的概念很簡單,幾乎是每個設計模式背后的精神所在。所有的模式都提供了一套方法讓系統中的某部分改變不會影響其他部分。
模擬鴨子程序中的變定分離
Duck類內的fly()和quack()會隨著鴨子的不同而改變。
為了要把這兩個行為從Duck類中分開 ,我們將把它們自Duck類中取出,建立一組新類代表每個行為。
設計鴨子兩個行為
我們希望一切能有彈性,我們還想能夠指定行為到鴨子的實例,比方說,想要產生綠頭鴨實例,并指定特定類型的飛行行為給它。
干脆順便讓鴨子的行為可以動態地改變好了。換句話說,我們應該在鴨子類中包含設定行為的方法,就可以在運行時動態地改變綠頭鴨的飛行行為。
設計原則:針對接口編程 ,而不是針對實現編程。
我們利用接口代表每個行為,比方說,FlyBehavior與QuackBehavior,而行為的每個實現都必須實現這些接口之一。所以這次鴨子類不會負責實現這接口,反而是由其他類專門實現FlyBehavior與QuackBehavior,這其他類就稱為行為類。由行為類實現行為接口,而不是由Duck類實現行為接口。
這樣的作法迥異于以往,以前的作法是:
行為是繼承Duck超類的具體實現而來,
繼承某個接口并由子類自行實現而來。
這兩種作法都是依賴于實現,我們被實現綁得死死的,沒辦法更改行為(除非寫更多代碼)。
在我們的新設計中,鴨子的子類將使用接口(FlyBehavior與QuackBehavior)所表示的行為,所以實際的實現不會被綁死在鴨子的子類中。(換句話說,特定的實現代碼位于實現FlyBehavior與QuakcBehavior的特定類中)。
設計原則:針對接口編程 ,而不是針對實現編程
針對接口編程真正的意思是針對超類型(supertype)編程。
這里所謂的接口有多個含意,接口是
- 一個概念,
- 一種Java的interface構造。
你可以在不涉及Java interface的情況下,針對接口編程,關鍵就在多態。
利用多態,程序可以針對超類型編程,執行時會根據實際狀況執行到真正的行為,不會被綁死在超類型的行為上。
針對超類型編程這句話,可以更明確地說成:變量的聲明類型,應該是超類型,通常是一個抽象類或者是一個接口,如此,只要是具體實現此超類型的類所產生的對象,都可以指定給這個變量;這也意味著,聲明類時,不用理會以后執行時的真正對象類型!
看看下面這個簡單的多態例子:假設有一個抽象類Animal,有兩個具體的實現(Dog與Cat)繼承Animal。
針對實現編程,作法如下:
Dog d = new Dog(); d.bark();聲明變量d為Dog類型(是Animal的具體實現),會造成我們必須針對實現編碼。
但是針對接口/超類型編程,作法會如同下面:
Animal animal = new Dog(); animal.makeSound();我們知道該對象是狗 , 但是我們現在利用animal進行多態的調用。
更棒的是,子類型實例化的動作不再需要在代碼中硬編碼,例如new Dog(),而是在運行時才指定具體實現的對象。
a = getAnimal(); a.makeSound();我們不知道實際的子類型是什么,我們只關心它知道如何正確地進行makeSound()的動作就夠了。
(MyNote:一開始編碼時寫具體實現沒有錯,中途有需求變化就積極重構吧!)
實現鴨子的行為
我們有兩個接口,FlyBehavior和QuackBehavior,還有它們對應的類,負責實現具體的行為:
這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象復用,因為這些行為已經與鴨子類無關了。
而我們可以新增一些行為 ,不會影響到既有的行為類 ,也不會影響有使用到飛行行為的鴨子類。
這么一來,有了繼承的復用好處,卻沒有繼承所帶來的包袱。(MyNote:順得哥情亦不失嫂意)
整合鴨子的行為
關鍵在于,鴨子現在會將飛行和呱呱叫的動作,委托(delegate)別人處理,而不是使用定義在自己類(或子類)內的方法。
然后實現performQuack() (performFly()類似)
public class Duck {QuackBehavior quackBehavior;// 還有更多public void performQuack() {//不親自處理呱呱叫行為,而是委托給quackBehavior對象。quackBehavior.quack();} }Duck的子類在構造器中賦值行為變量
public class MallardDuck extends Duck {public MallardDuck() {quackBehavior = new Quack();flyBehavior = new FlyWithWings();}public void display() {System.out.println(“I’m a real Mallard duck”);} }放碼過來
鴨子抽象類
public abstract class Duck {FlyBehavior flyBehavior;QuackBehavior quackBehavior;public Duck() {}public void setFlyBehavior(FlyBehavior fb) {flyBehavior = fb;}public void setQuackBehavior(QuackBehavior qb) {quackBehavior = qb;}abstract void display();public void performFly() {flyBehavior.fly();}public void performQuack() {quackBehavior.quack();}public void swim() {System.out.println("All ducks float, even decoys!");} }誘騙鴨
public class DecoyDuck extends Duck {public DecoyDuck() {setFlyBehavior(new FlyNoWay());setQuackBehavior(new MuteQuack());}public void display() {System.out.println("I'm a duck Decoy");} }綠頭鴨
public class MallardDuck extends Duck {public MallardDuck() {quackBehavior = new Quack();flyBehavior = new FlyWithWings();}public void display() {System.out.println("I'm a real Mallard duck");} }紅頭鴨
public class RedHeadDuck extends Duck {public RedHeadDuck() {flyBehavior = new FlyWithWings();quackBehavior = new Quack();}public void display() {System.out.println("I'm a real Red Headed duck");} }橡皮鴨
public class RubberDuck extends Duck {public RubberDuck() {flyBehavior = new FlyNoWay();quackBehavior = new Squeak(); // quackBehavior = () -> System.out.println("Squeak");}public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {this.flyBehavior = flyBehavior;this.quackBehavior = quackBehavior; }public void display() {System.out.println("I'm a rubber duckie");} }飛行行為接口
public interface FlyBehavior {public void fly(); }不能飛行行為類
public class FlyNoWay implements FlyBehavior {public void fly() {System.out.println("I can't fly");} }能飛行行為類
public class FlyWithWings implements FlyBehavior {public void fly() {System.out.println("I'm flying!!");} }叫聲行為接口
public interface QuackBehavior {public void quack(); }不叫行為類
public class MuteQuack implements QuackBehavior {public void quack() {System.out.println("<< Silence >>");} }呱呱叫行為類
public class Quack implements QuackBehavior {public void quack() {System.out.println("Quack");} }吱吱叫行為類
public class Squeak implements QuackBehavior {public void quack() {System.out.println("Squeak");} }運行測試類
public class MiniDuckSimulator {public static void main(String[] args) {Duck mallard = new MallardDuck();mallard.performQuack();mallard.performFly();System.out.println("---");Duck model = new RedHeadDuck();model.performFly();//改變飛行行為model.setFlyBehavior(new FlyNoWay());model.performFly();} }運行結果:
I'm Quack I'm flying!! --- I'm flying!! I can't fly本例子類與接口全家福
設計原則:多用組合,少用繼承
根據本例,使用組合建立系統具有很大的彈性,不僅可將算法族封裝成類 ,更可以在運行時動態地改變行為,只要組合的行為對象,符合正確的接口標準即可。
參考資料
總結
以上是生活随笔為你收集整理的Java设计模式(1 / 23):策略模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于接地:数字地、模拟地、信号地、交流地
- 下一篇: 《Java8实战》笔记(09):默认方法