策略模式(Strategy Pattern)
策略模式定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變化獨立與使用算法的客戶。
從一個簡單的應用說起。
背景
假如我們要設計一款鴨子模擬器。有綠頭鴨,紅頭鴨,玩具鴨,橡皮鴨等。鴨子的行爲有呱呱叫,游泳,飛翔等。根據簡單的面向對象設計思路。我們先拿出第一版設計圖。
第一版
第一版,我們抽象了一個鴨子超類。其他所有類型鴨子從超類繼承。超類有呱呱叫(quack)和游泳(swim)方法。
好景不長,很快,我們的新需求出現了,鴨子需要飛行技能,於是我們故技重施,給鴨子超類添加fly()方法。
出現第二版:
第二版
此時,所有的鴨子子類都會繼承fly()方法,但問題是,並不是所有但鴨子都能飛。比如橡皮鴨子就不會飛。還發現了其他潛藏的問題,橡皮鴨子也不會呱呱叫。所以橡皮鴨必須將quack()覆蓋成“吱吱叫”(squeak)
當涉及“維護”時,爲了“複用”(reuse)的目的而使用繼承,結局並不完美。
臨時方案:各子類根據情況覆蓋超類方法
第三版
此時,發現子類中,各種鴨子的差異很大,冗餘的“什麼都不做”代碼有點多。我們試着將fly()方法和quack()方法抽象爲接口,讓有需要的子類去自行實現這兩個接口如何?
第四版
你覺得這個設計如何?
雖然剝離出的兩個接口Flyable和Quackable從某種程度解決了一些代碼冗餘的問題,但造成了新的問題:代碼無法複用。每新建一個子類,都需要既繼承Duck超類又實現接口。直到。。。。。。。
身騎白馬—策略模式
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
是時候把鴨子的行爲從Duck類中取出來了。
我們知道 Duck類內的fly()和quack()會隨着鴨子的不同而改變。
爲了要不這兩個行爲從Duck類中分開,我們要把它們從Duck類中取出來,建立一組新類來代表每個行爲。
那麼如何設計實現飛行和呱呱叫的行爲類呢?
針對接口編程,而不是針對實現編程
爲了靈活的設計,從現在開始,鴨子的子類將使用接口(FlyBehavior與QuackBehavior)所表示的行爲,實際的“實現”不會被綁死在鴨子的子類中。
如圖:
“針對接口編程”真正的意思是“針對超類型(supertype)編程。
這樣的設計,可以讓飛行和呱呱叫的動作被其他對象複用,因爲這些行爲已經與鴨子類無關了。
而我們可以新增一些行爲,不會影響到既有的行爲類,也不會影響“使用”到飛行行爲的鴨子類。
整合鴨子的行爲
關鍵在於,鴨子現在會將飛行和呱呱叫的動作“委託”(delegate)別人處理,而不是使用定義在Duck類(或子類)內的呱呱叫和飛行方法。
代碼實現
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-21:57
*/
public abstract class Duck {
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 void setFlyBehavior(FlyBehavior fb){
//可隨時通過該方法修改子類鴨子的飛行行爲
flyBehavior=fb;
}
public void setQuackBehavior(QuackBehavior qb){
// 可隨時通過該方法修改子類鴨子的鳴叫行爲
quackBehavior=qb;
}
}
package strategy;
public interface QuackBehavior {
public void quack();
}
package strategy;
public interface FlyBehavior {
public void fly();
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:01
*/
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I cat't fly");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:21
*
* 建立一個利用火箭動力的飛行行爲
*/
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with a rocket!!");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-21:59
*/
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm Flying!!!");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:01
*/
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("strategy.Quack");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:03
*/
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Slience >>");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:08
*/
public class MallarDuck extends Duck {
public MallarDuck(){
quackBehavior=new Quack(); //TODO 這裏不夠靈活
flyBehavior=new FlyWithWings();//TODO 這裏不夠靈活
}
@Override
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:18
*/
public class ModelDuck extends Duck {
public ModelDuck(){
flyBehavior=new FlyNoWay();//模型鴨是不會飛的
quackBehavior=new Quack();//模型鴨會叫
}
@Override
public void display() {
System.out.println("I'm a model duck");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:11
*
* 測試類
*/
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard=new MallarDuck();
mallard.performFly();
mallard.performQuack();
Duck model=new ModelDuck();
model.performFly();//模型鴨默認不會飛
model.setFlyBehavior(new FlyRocketPowered());//給模型鴨設定火箭動力的飛行行爲
model.performFly();//模型鴨 動態改變了飛行行爲
}
}