設計模式中的多態——策略模式詳解

1. 關於策略模式

策略模式和java語言的多態特性有些像。java的多態特性容許咱們面向接口編程,不用關心接口的具體實現。接口所指向的實現類,以及經過接口調用的方法的具體行爲能夠到運行時才綁定。這麼作最大的好處是在儘量實現代碼複用的前提下更好地應對具體實現類的變化。好比我想增長一種接口的實現或者修改原有實現類的某個行爲,那我幾乎不用修改任何客戶端代碼。策略模式能夠說正是這種思想在設計模式上的運用。它可使咱們更好的複用代碼,同時使程序結構設計更有彈性,更好的應對變化。算法

2. 策略模式詳解

2.1 策略模式定義

策略模式定義了一系列算法,並將每個算法封裝起來,並且使它們還能夠相互替換。策略模式讓算法獨立於使用它的客戶端而獨立的變化。

可使用多態進行類比來理解策略模式的定義。一系列算法能夠理解成接口的不一樣實現類,由於不一樣實現類都實現了相同的接口,於是它們也能夠相互替換。策略模式讓算法獨立於客戶端而變化與接口的實現類能夠獨立於使用接口的客戶端變化相似。編程

2.2 策略模式的UML類圖

從UML類圖上能夠看出,策略模式中主要有3個角色設計模式

  • 抽象策略接口
    上圖中的Strategy即抽象策略接口,接口中定義了抽象的策略算法algorithm()。ide

  • 具體的策略實現類
    上圖中的StrategyA和StrategyB即具體的策略實現。不一樣的策略實現類都實現了抽象策略接口,並重寫了其抽象策略方法。由於都實現了相同的策略接口,於是算法能夠相互替換,而且能夠動態的改變具體的算法實現。優化

  • 封裝策略的上下文環境
    上圖中的Context即策略的上下文環境。它屏蔽了高層模塊對策略算法的直接訪問,封裝了可能存在的變化。並且提供了修改Strategy的setter方法,能夠動態的改變算法的具體實現。this

3.策略模式的優勢

咱們能夠結合使用策略模式的例子並與其它實現方案進行對比來看看策略模式到底有什麼好處編碼

3.1 一個使用策略模式的例子

定義一個汽車類Car。因爲汽車最大的特色是能跑,於是咱們賦予該類一個move行爲。但要跑起來須要提供能源,一般而言這種能源是汽油,但如今純靠電池驅動的汽車也愈來愈多。於是Car的move行爲就有兩種不一樣的行爲,一種是使用汽油跑,一種是使用電能跑。於是咱們能夠這麼定義設計

  • 抽象的汽車類Car
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public abstract class Car {

    //汽車品牌
    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }

    public Car(String brand, MoveStrategy strategy) {
        this.brand = brand;
        this.moveStrategy=strategy;
    }

    //汽車的運行策略:使用汽油運行,使用電能運行等等
    private MoveStrategy moveStrategy;

    //運行方法
    public void move() {
        System.out.print(brand);
        moveStrategy.move();
    }

    public void setMoveStrategy(MoveStrategy moveStrategy) {
        this.moveStrategy = moveStrategy;
    }
}

在抽象汽車類中定義了一個move()方法表示汽車具備運行的行爲,可是因爲究竟是使用汽油運行仍是使用電能運行並無直接定義在裏面,而是調用了策略接口中定義的move方法。該策略接口以組合的方式封裝在Car內部,並提供了setter方法供客戶端動態切換汽車的運行方式。code

  • 使用汽油運行的策略實現
/**
 * @author: takumiCX
 * @create: 2018-10-14
 **/

/**
 * 使用汽油運行的策略實現
 */
public class GasolineMoveStrategy implements MoveStrategy{

    @Override
    public void move() {
        System.out.println(" Use Gasoline Move!");
    }
}
  • 使用電池運行的策略實現
/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/

/**
 * 使用電能運行的策略實現
 */
public class ElectricityMoveStrategy implements MoveStrategy {
    @Override
    public void move() {
        System.out.println(" Use Electricity Move!");
    }
}
  • 具體的汽車實現類
    好比咱們經過繼承的方式定義一輛特斯拉汽車,特斯拉汽車默認是純電動的
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class TeslaCar extends Car {

    public TeslaCar(String brand) {
        super(brand,new ElectricityMoveStrategy());
    }
}
  • 客戶端代碼
    首先構造一輛特斯拉車觀察其運行方式,並經過setter方法動態改變其運行方式爲汽油驅動
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class Client {

    public static void main(String[] args) {

        TeslaCar car = new TeslaCar("Tesla");

        car.move();

        car.setMoveStrategy(new GasolineMoveStrategy());

        car.move();
    }
}
  • 運行結果

3.2 與其餘實現方式的對比

其實上面的例子除了使用策略模式外,還有其餘實現方式,但它們都有比較明顯的缺點。

3.2.1接口的實現方式

/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/
public interface Move {
    
    void move();
}

並讓抽象父類Car實現它

/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public abstract class Car implements Move{
    //汽車品牌
    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }
}

這樣全部繼承Car的具體汽車類都必須實現本身的move方法,也就是讓具體的汽車子類來決定汽車的具體行爲:究竟是使用汽油運行仍是使用電池運行。可是這麼作至少有如下幾個缺點

  • 1.具體的汽車運行行爲不方便後期維護。於是move行爲沒法被複用,具體的實現都分散在了子類中。若是要對某種驅動方式的實現進行修改,不得不修改全部子類,這簡直是災難。

  • 2.致使類數量的膨脹。一樣品牌的汽車,因爲有汽油和電動兩種運行方式,不得不爲其維護兩個類,若是在增長一種驅動方式,好比氫能源驅動,那不得爲每一個品牌的汽車再增長一個類。

  • 3.不方便move行爲的擴展,也不方便動態的更換其實現方式。

3.2.2 if-else的實現方式

move方法接受客戶端傳遞的參數,經過if-else或者swich-case進行判斷,選擇正確的驅動方式。

public void move(String moveStrategy) {
    if("electricity".equals(moveStrategy)){
        System.out.println(" Use Electricity Move!");
    }else if("gasoline".equals(moveStrategy)){
        System.out.println(" Use Gasoline Move!");
    }
}

但這樣作至關於硬編碼,不符合開閉原則。好比我要增長一種氫能源的驅動方式,這種實現就須要修改move中的代碼。而若是使用上面說的策略模式,則只須要增長一個實現實現策略接口的具體策略實現類,而不須要修改move中的任何代碼,便可被客戶端所使用。

/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/
public class HydrogenMovetrategy implements MoveStrategy {
    @Override
    public void move() {
        System.out.println(" Use Hydrogen Move!");
    }
}

3.3 使用策略模式的優勢

  • 1.能夠優化類結構,當類的某種功能有多種實現時,能夠在類中定義策略接口,將真正的功能實現委託給具體的策略實現類。這樣避免了類膨脹,也能更好的進行擴展和維護。

  • 2.避免使用多重條件判斷致使的硬編碼和擴展性差的問題

  • 3.可使具體的算法實現自由切換,加強程序設計的彈性。

4. 使用工廠方法模式改進原有策略模式

全部的策略實現都須要對外暴露,上層模塊必須知道具體的策略實現類,這與迪米特法則相違背。爲此,可使用工廠方法模式進行解耦。

  • 策略工廠接口
/**
 * @author: takumiCX
 * @create: 2018-10-16
 **/
public interface MoveStrategyFactory {

    MoveStrategy create();
}
  • 氫能源驅動方式的工廠
/**
 * @author: takumiCX
 * @create: 2018-10-16
 **/
public class HydrogenMoveStrategyFactory implements MoveStrategyFactory {
    @Override
    public MoveStrategy create() {
        return new HydrogenMovetrategy();
    }
}
  • 客戶端
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class Client {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        TeslaCar car = new TeslaCar("Tesla");

        MoveStrategyFactory factory = new HydrogenMoveStrategyFactory();

        MoveStrategy moveStrategy = factory.create();

        car.setMoveStrategy(moveStrategy);

        car.move();

    }
}

這樣咱們經過工廠方法模式封裝了具體策略類的建立過程,同時也避免了向高層模塊暴露。最後運行結構以下

5. 總結

當完成某項功能有多種不一樣的實現時,能夠實用策略模式。策略模式封裝了不一樣的算法,而且使這些算法能夠相互替換,這提升了代碼的複用率也加強了程序設計的彈性。而且能夠結合其餘設計模式好比工廠方法模式向上層模塊屏蔽具體的策略類,使代碼更易於擴展和維護。

5. 參考資料

  • 《Head First 設計模式》
  • 《設計模式之禪》
相關文章
相關標籤/搜索