关于设计模式——策略模式-Strategy Pattern
文章目錄
- 1 策略模式
- 1.1 模擬鴨子
- 1.2 設(shè)計(jì)原則
- 1.3 整合行為
- 1.4 模擬鴨子代碼的代碼
- 1.5 動(dòng)態(tài)設(shè)定行為
- 1.6 重新查看整體
- 1.7 繼承和組合
- 1.8 總結(jié)
- 1.9 優(yōu)劣期間應(yīng)用場(chǎng)景
- 2.0 參照資料
1 策略模式
在我們什么都不會(huì)的情況下,我們先無(wú)需知道什么是策略模式,我們要做的是:在了解什么是設(shè)計(jì)模式之前,讓我們先引入一個(gè)例子。
1.1 模擬鴨子
如果要用java做一個(gè)模擬鴨子的游戲,那么對(duì)于剛學(xué)完JavaSE的人來(lái)說(shuō),它會(huì)想的就是設(shè)計(jì)一個(gè)鴨子的超類(Duck),然后讓其他的鴨子繼承此類。其中,所有的鴨子都會(huì)叫(Quack)和游泳(Swin)。而每一種鴨子的外觀不同,所以display方法是抽象的,每個(gè)鴨子子類要在自己的類上實(shí)現(xiàn)display的具體外觀。
后面主管下來(lái)要求,說(shuō)這個(gè)鴨子必須得會(huì)飛了。所以為了速度,我們直接在超類Duck上加上fly方法,這樣,所有的鴨子都會(huì)飛了。
但是問題出現(xiàn)了,模擬鴨子的游戲中有一種橡皮鴨子,這種鴨子作為玩具是不會(huì)飛的,如果在Duck上面直接加fly就會(huì)導(dǎo)致橡皮鴨子也會(huì)飛,而如果不在Duck上面加則需要去其他會(huì)飛的鴨子類上一個(gè)一個(gè)寫fly方法。而且對(duì)于橡皮鴨來(lái)說(shuō),其并不像真鴨一樣會(huì)嘎嘎叫,而是吱吱叫(squeak)。
或許有一種方法可以解決上述問題,可以在Duck上加fly方法,然后在橡皮鴨子類上重寫該方法,使它不會(huì)飛即可。雖然這種做法能夠解決一時(shí)的問題,但是后面如果又加入其他有特色的鴨,那就會(huì)導(dǎo)致代碼復(fù)用性不高,代碼冗余。
還有一種辦法是,fly方法不寫在超類里,而是寫在接口里,哪只鴨子會(huì)飛就去調(diào)用。明顯地,這種做法對(duì)于鴨子多數(shù)會(huì)飛的情況簡(jiǎn)直是噩夢(mèng)。
1.2 設(shè)計(jì)原則
為此,我們引入第一個(gè)設(shè)計(jì)原則:
如果應(yīng)用中有可能變化的地方,把他找出來(lái)并且獨(dú)立,不要和那些不需要變化的代碼混在一起。
既然fly和quack都有可能變化,那我們干脆把它們獨(dú)立出來(lái)。我們可以建立兩組類,一組是實(shí)現(xiàn)fly的,一組實(shí)現(xiàn)quack。在每組類中實(shí)現(xiàn)各自的動(dòng)作。我們把這種獨(dú)立出來(lái)具有多種選擇的類叫做策略類,對(duì)應(yīng)策略類中算法的不同,每組類都含有許多算法,我們把某組類叫做算法族。
我們?cè)谇懊孀龅囊磺卸际菫榱宋覀冊(cè)O(shè)計(jì)的鴨子有彈性,即可以各式各樣。而各式各樣的原因大多是因?yàn)樾袨椴灰?。既然行為不一?#xff0c;那我們?cè)贒uck類中就應(yīng)該包含設(shè)定行為的方法,使其可以動(dòng)態(tài)地改變鴨子的行為。
為此,我們引入第二個(gè)設(shè)計(jì)原則:
針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。
但是與前面提到的不一樣的是,我們?cè)O(shè)計(jì)的接口并不是接在實(shí)例類上的,而是接在行為類上的。這樣的話,鴨子的行為將被放在分開的類中,此類專門提供某行為的實(shí)現(xiàn)。這樣的話鴨子類就不需要知道行為的實(shí)現(xiàn)細(xì)節(jié)。
我們現(xiàn)在來(lái)體會(huì)一個(gè)例子,假如現(xiàn)在有一個(gè)抽象類Animal,有兩個(gè)具體的實(shí)現(xiàn)Dog和Cat,這兩個(gè)類都是基礎(chǔ)Animal。
//Animal make Sound(); //指定抽象類叫聲//Dog makeSound(){bark(); //汪汪叫 } bark();//Cat makeSound(){meow(); //喵喵叫 } meow();如果是針對(duì)實(shí)現(xiàn)編程,那我們會(huì)做如下所示的事:
Dog d = new Dog(); d.bark();如果是針對(duì)接口/超類型編程,那我們會(huì)做如下所示的事:
Animal animal = new Dog(); animal.makeSound();也就是說(shuō),我們并不關(guān)心子類是什么,只關(guān)心子類能否正確的實(shí)現(xiàn)其對(duì)應(yīng)的父類行為即可。
現(xiàn)在讓我們回到鴨子。如果我們有兩個(gè)接口,一個(gè)接口是飛接口(FlyBehavior),還有一個(gè)叫接口(QuackBehavior)。我們還有兩個(gè)對(duì)應(yīng)的行為類,其負(fù)責(zé)實(shí)現(xiàn)具體的行為。如圖所示:
這樣的設(shè)計(jì)使得飛行類組合叫聲類組都已經(jīng)和鴨子類無(wú)關(guān)了,甚至于,我們還可以設(shè)計(jì)多種新的行為,比如鴨子會(huì)跳舞。
或許這樣新奇的設(shè)計(jì)會(huì)讓人感到奇怪,用類代表行為而不是代表東西,這樣是否不太恰當(dāng)。實(shí)際上,行為也是可以理解為東西的,看具體情況而定,例如在鴨子的問題上,鴨子的飛行類可以有翅膀每秒拍動(dòng)幾下、飛的最大高度、飛的速度等行為屬性。
1.3 整合行為
現(xiàn)在我們已經(jīng)設(shè)計(jì)好了,該整合了。鴨子不太可能變化的行為處于自身超類Duck,而鴨子變化的類叫聲類和飛行類被我們分離出兩組類來(lái),一個(gè)類對(duì)應(yīng)一個(gè)情況。
接下來(lái),我們需要?jiǎng)討B(tài)控制鴨子的飛行和叫聲行為。我們把Duck類中和其子類中所有的fly和quack移除,然后用peerformFly和performQuack來(lái)取代Duck類中的fly和quack。
也就是說(shuō),Duck類里面現(xiàn)在有如下的東西:
//Duck FlyBehavior flyBehavior //接口 QuackBehavior quackBehavior //接口performQuack() swin() display() performFly()我們?cè)趐erformFly和performQuack兩個(gè)方法中委托兩個(gè)行為接口來(lái)幫我們做叫和飛這兩件事,如下:
public void performQuack() {quackBehavior.quack(); }整合完成后。我們來(lái)嘗試創(chuàng)建一個(gè)實(shí)例類,比如綠頭鴨(MallardDuck)。
public class MallardDuck extends Duck {public MallardDuck(){quackBehavior = new Quack(); //會(huì)呱呱叫flyBehavior = new FlyWithWings(); //會(huì)飛}public void display(){System.out.println("i'm a real Mallard duck");} }1.4 模擬鴨子代碼的代碼
/*鴨子超類*/ public abstract class Duck {//聲明兩個(gè)接口變量FlyBehavior flyBehavior;QuackBehavior quackBehavior;public Duck(){}public 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 interface FlyBehavior {void fly(); }/*鴨子叫聲接口*/ public interface QuackBehavior {void quack(); }/*鴨子飛行類*/ public class FlyWithWings implements FlyBehavior {@Overridepublic void fly() {System.out.println("I'm flying!");} }/*鴨子不能飛行類*/ public class FlyNoWay implements FlyBehavior{@Overridepublic void fly() {System.out.println("I can't fly");} }/*鴨子呱呱叫類*/ public class Quack implements QuackBehavior{@Overridepublic void quack() {System.out.println("Quack");} }/*鴨子吱吱叫類*/ public class Squeak implements QuackBehavior{@Overridepublic void quack() {System.out.println("Squeak");} }/*鴨子不會(huì)叫類*/ public class MuteQuack implements QuackBehavior{@Overridepublic void quack() {System.out.println("<<Silence>>");} }/*實(shí)例綠頭鴨*/ public class MallardDuck extends Duck{@Overridepublic void display() {System.out.println("I'm a real Mallard duck");}public MallardDuck(){quackBehavior = new Quack();flyBehavior = new FlyWithWings();} }/*測(cè)試類*/ public class Main {public static void main(String[] args) {Duck mallard = new MallardDuck();mallard.performQuack();mallard.performFly();} }1.5 動(dòng)態(tài)設(shè)定行為
在Duck類中,我們加入兩個(gè)新方法,這兩個(gè)方法的好處是,通過調(diào)用這兩個(gè)方法傳入一個(gè)匿名內(nèi)部類,該內(nèi)部類實(shí)現(xiàn)了飛行接口,這樣通過了接口多態(tài)的形式改變了實(shí)例類內(nèi)部提前設(shè)定好的特性,從而提高了動(dòng)態(tài)設(shè)定行為的動(dòng)態(tài)性。
public void setFlyBehavior(FlyBehavior fb) {flyBehavior = fb; }public void setQuackBehavior(QuackBehavior qb) {quackBehavior = qb; }如果會(huì)看前面的綠頭鴨,我們會(huì)發(fā)現(xiàn)我們前面設(shè)定行為是在實(shí)例類里面的,而結(jié)果上面代碼的操作,我們可以通過插入一個(gè)新的功能類來(lái)改變?cè)仍O(shè)定好的功能,這樣耦合性提高了。
public class MallardDuck extends Duck
{
public MallardDuck(){
quackBehavior = new Quack(); //會(huì)呱呱叫
flyBehavior = new FlyWithWings(); //會(huì)飛
}
? public void display(){
? System.out.println(“i’m a real Mallard duck”);
? }
}
現(xiàn)在我們可以隨意調(diào)動(dòng)上面的兩個(gè)方法來(lái)控制新的實(shí)例鴨子的行為了,我們建立一個(gè)模型鴨來(lái)驗(yàn)證我們的說(shuō)法。
public class ModelDuck extends Duck{public ModelDuck() {flyBehavior = new FlyNoWay();quackBehavior = new Quack();}@Overridepublic void display() {System.out.println("I'm a model duck");} }建立一個(gè)新的行為,這種行為是給鴨子綁定一個(gè)火箭讓其飛行。
public class FlyRocketPowered implements FlyBehavior{@Overridepublic void fly() {System.out.println("I'm flying with a rocket!");} }在上面模型鴨的設(shè)定中,它原本是不會(huì)飛的,但是我們可以在設(shè)置飛行的方法中傳入?yún)?shù),使其能夠飛。
Duck model = new ModelDuck(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly();1.6 重新查看整體
1.7 繼承和組合
實(shí)際上,在開發(fā)中應(yīng)該轉(zhuǎn)變一些思維方式。比如鴨子是一個(gè)會(huì)飛的生物(繼承),我們重新看做鴨子有一個(gè)會(huì)飛的技能會(huì)更好(組合)。這也是我們第三個(gè)設(shè)計(jì)原則:
多用組合,少用繼承。
是的,很多人都會(huì)有疑惑,實(shí)際上用繼承寫起代碼來(lái)更加地簡(jiǎn)潔,但是后期維護(hù)起來(lái)確是組合要更加地方便。
1.8 總結(jié)
是的,對(duì)于不熟悉上面流程的同學(xué),可以從頭到尾再看一遍,上面的鴨子代碼中,用到了我們學(xué)的第一個(gè)設(shè)計(jì)模式:策略模式。不僅如此,我們還用到了三個(gè)設(shè)計(jì)原則,讓我們重新來(lái)認(rèn)識(shí)一下它們吧。
- 單一職責(zé)原則(Single Responsibility Principle, SRP):一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個(gè)類而言,應(yīng)該只有一個(gè)引起它變化的原因。
- 依賴倒轉(zhuǎn)原則(Dependency Inversion Principle, DIP):抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。換言之,要針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。
- 合成復(fù)用原則(Composite Reuse Principle, CRP):盡量使用對(duì)象組合,而不是繼承來(lái)達(dá)到復(fù)用的目的。
當(dāng)然,在這里我們不細(xì)講這些原則,只是了解了個(gè)大概,在后面的學(xué)習(xí)中我們會(huì)詳細(xì)闡述,現(xiàn)在重要的問題是,我們應(yīng)該回過頭重新審視一下策略模式。
在軟件開發(fā)中,我們常常會(huì)遇到類似的情況,實(shí)現(xiàn)某一個(gè)功能有多條途徑,每一條途徑對(duì)應(yīng)一種算法,此時(shí)我們可以使用一種設(shè)計(jì)模式來(lái)實(shí)現(xiàn)靈活地選擇解決途徑,也能夠方便地增加新的解決途徑,這種模式我們叫做策略模式。
策略模式實(shí)用性強(qiáng),擴(kuò)展性好,在軟件開發(fā)中得以廣泛使用,是使用頻率較高的設(shè)計(jì)模式之一。其典型的應(yīng)用莫過于Sun公司開發(fā)JavaSE中的容器布局管理了。其基本結(jié)構(gòu)示意圖如下所示:
對(duì)于容器環(huán)境來(lái)說(shuō),容器環(huán)境該有的組件基本不變,所以將JPanel放在Container下沒什么毛病,而對(duì)于布局Layout來(lái)說(shuō),布局有各式各樣的布局,根據(jù)對(duì)應(yīng)的軟件開發(fā)來(lái)選擇合適的布局,其中LayoutManger作為接口充當(dāng)了抽象策略角色,當(dāng)我們需要哪類具體的情況,只需要調(diào)用其子類即可。
1.9 優(yōu)劣期間應(yīng)用場(chǎng)景
從上面的一系列場(chǎng)景可以看出,策略模式適用處理較為靈活問題。譬如想要方便擴(kuò)展、更換、修改某類功能,那么你就可以把功能對(duì)應(yīng)的算法封裝起來(lái),然后采用策略模式。
主要優(yōu)點(diǎn)
策略模式提供了對(duì)開閉原則的完美支持。代碼書寫人員在不修改以前代碼的情況下,也能擴(kuò)展新的功能。
策略模式可以管理相關(guān)的算法族。如上所述,策略模式可以定義一個(gè)接口來(lái)管理旗下的算法族,從而達(dá)到動(dòng)態(tài)控制的效果。
策略模式提供了一種可以替換繼承關(guān)系的辦法。如果隨時(shí)變化的功能和不太可能變化的功能混寫于一個(gè)類中,那么這不符合單一職責(zé)原則。但是使用策略模式可以提供一種動(dòng)態(tài)的切換效果。
使用策略模式可以避免多重條件選擇語(yǔ)句?;叵肫鹎懊娴哪M鴨子游戲,如果不使用算法族,那么我們需要使用大量的控制語(yǔ)句來(lái)判斷鴨子到底應(yīng)該有什么功能。
主要缺點(diǎn)
代碼書寫人必須知道所有的策略類。如上模擬鴨子的代碼中,如果你不知道模型鴨有綁定火箭飛行這個(gè)策略類,那你就無(wú)法調(diào)用。
策略類容易造成控制粒度過細(xì)。如果每個(gè)變化就要寫一個(gè)策略類,那么寫的代碼就會(huì)過多。
無(wú)法同時(shí)使用同個(gè)功能下的多個(gè)策略類。如果你的飛行接口下只規(guī)定了三種情況,飛行、不會(huì)飛、綁定火箭飛行。那么你的實(shí)例類是不可以兼具一個(gè)接口下管理的多個(gè)策略類的,換言之,你的鴨不可能不會(huì)飛又會(huì)飛。
適用場(chǎng)景
一個(gè)功能需要?jiǎng)討B(tài)地選擇不同的策略。如果是這種情況,你可以把某個(gè)功能對(duì)應(yīng)的不同情況寫入策略類,然后根據(jù)面向?qū)ο蟮亩鄳B(tài)性來(lái)選擇具體的策略算法。
一個(gè)對(duì)象由很多行為。比如博主開發(fā)的Galgame游戲就是因?yàn)橛螒蚬δ苓^多,每個(gè)功能的行為也有很多,從而使用了策略模式。
不希望使用者知道復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。在具體策略類中封裝算法與相關(guān)的數(shù)據(jù)結(jié)構(gòu),可以提高算法的保密性和安全性。
2.0 參照資料
算法的封裝與切換——策略模式(四)_劉偉技術(shù)博客-CSDN博客_算法切換
HeadFirst設(shè)計(jì)模式——策略模式
本人技術(shù)有限,如果博客有語(yǔ)法或者文字錯(cuò)誤,歡迎指正!
總結(jié)
以上是生活随笔為你收集整理的关于设计模式——策略模式-Strategy Pattern的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: #pragma comment (lib
- 下一篇: .NET设计模式(7):创建型模式专题总