1. 程式人生 > 實用技巧 >設計模式--策略模式

設計模式--策略模式

策略模式

  • 定義

策略模式是定義了演算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓演算法的變化可以獨立於使用演算法的客戶。

先不用著急理解定義,先看下面的例子

  • 栗子

假設我們有一個Car類(代表所有的汽車),我們知道汽車都可以發動、加速、剎車等,而現在的汽車種類有非常非常多,不同的品牌,使用不同的燃料等等,於是,為了提高程式碼的複用性,我們可以將所有汽車都具有的共性封裝到Car類中,不同型別的車有各自的類,他們的個性就在各自的類中實現即可。程式碼如下:

Car類

public class Car{

    /**
     * 發動
     */
    public void start(){
        System.out.println("發動");
    }

    /**
     * 加速
     */
    public void accele(){
        System.out.println("加速");
    }

    /**
     * 剎車
     */
    public void brake(){
        System.out.println("剎車");
    }
}

寶馬汽車類

/**
 * 賓士 繼承Car類
 */
public class BWMCar extends Car{
    
    /**
     * 檢視汽車品牌
     */
    public void getBrand(){
        System.out.println("寶馬");
    }

    /**
     * 補充燃料
     */
    public void refuel(){
        System.out.println("加油");
    }
}

特斯拉汽車類

/**
 * 特斯拉 繼承Car
 */
public class TeslaCar extends Car{
    /**
     * 檢視汽車品牌
     */
    public void getBrand(){
        System.out.println("特斯拉");
    }

    /**
     * 補充燃料
     */
    public void refuel(){
        System.out.println("充電");
    }
}

測試執行類

public class Test {
    public static void main(String[] args) {
        BWMCar bwm = new BWMCar();
        TeslaCar tesla = new TeslaCar();

        System.out.println("=======寶馬=======");
        bwm.start();
        bwm.accele();
        bwm.brake();
        bwm.getBrand();
        bwm.refuel();

        System.out.println("========特斯拉========");
        tesla.start();
        tesla.accele();
        tesla.brake();
        tesla.getBrand();
        tesla.refuel();
        
    }
}

執行結果:

=======寶馬=======
發動
加速
剎車
寶馬
加油
========特斯拉========
發動
加速
剎車
特斯拉
充電

類圖

對於現在來講這樣做已經可以了,但是,開發過程中從來不缺新需求的提出。假設此時比亞迪的電動車也要新增上,如果我們繼續按照上賣弄繼承的方式,可以得到下面比亞迪電動車類。

public class BYDCar extends Car{
    /**
     * 檢視汽車品牌
     */
    public void getBrand(){
        System.out.println("比亞迪");
    }

    /**
     * 補充燃料
     */
    public void refuel(){
        System.out.println("充電");
    }
}

我們可以看到,BYDCar 類和TeslaCar類除了getBrand() 的方法不一樣外,refuel() 方法是一模一樣的,那麼我們是不是可以將這個共有的方法提取出來呢?

提取到Car中顯然是不可以的,因為BWMCar 同時也繼承的Car類,如果將refuel()【充電】方法提取到Car中,那麼所有補充燃料方式不是充電的Car的子類都需要重寫refuel()方法,當子類特別多的時候,將會是非常麻煩的事情,後期如果有變動,修改起來也會令人瘋狂!

這時候你可能會有這樣的想法,我們可以再設計出兩個子類,一個作為汽油車的父類,一個作為電動車的父類,由這兩個類繼承Car,得到的類圖如下所示

看似這樣是可以的,但是我們知道比亞迪也有傳統的汽油車,那用繼承該如何實現呢?每個品牌下都有很多型號的汽車,用繼承全部實現可以嗎?

不論是如何實現,我們從上面兩次實現中都發現了繼承的缺陷,顯然,繼承很難滿足快速變化的需求,或者說繼承不是最佳的實現方式!

分析上面類圖可以發現,子類很多的方法都是相同的,只是具體的實現不同。是不是非常熟悉的感覺,沒錯,就是介面!我們接下來的解決方法就是使用介面來實現。

首先先抽離獲取品牌的方法,我們知道,汽車的品牌有很多,同時每個品牌又有非常多的型號的汽車,所以品牌非常有必要抽離!

我們抽離出一個介面Brand,所有的汽車品牌都需要實現這個介面,而具體的實現都是由具體的品牌自己確定。

同理,可以抽離出燃料補充介面:

同時我們再Car類中新增這兩種行為的屬性,同時抽離這兩種行為,並將其委託給具體的行為介面實現類去執行

完整的類圖如下:

再這裡使用的是組合來整合所有的行為,而不是繼承,下面用程式碼來實現吧

Brand介面

/**
 * 品牌介面
 */
public interface Brand {

    void getBrand();
    
}

品牌實現類

/**
 * 寶馬品牌實現類 繼承品牌介面
 */
public class BWM implements Brand{
    
    public void getBrand(){
        System.out.println("寶馬");
    }
}
public class Tesla implements Brand{
    
    public void getBrand(){
        System.out.println("特斯拉");
    }
}

public class BYD implements Brand{
    
    public void getBrand(){
        System.out.println("比亞迪");
    }
}

補充燃料介面

/**
 * 補充燃料介面
 */
public interface RefuelWay {
    
    void refuel();
}

補充燃料實現介面

/**
 * 加油補充燃料實現類
 */
public class Oil implements RefuelWay{
    
    public void refuel(){
        System.out.println("加油");
    }
}

public class Electricity implements RefuelWay{
    
    public void refuel(){
        System.out.println("充電");
    }
}

Car實現類

public class Car{

    /**
     * 展示品牌行為
     */
    private Brand brand;

    /**
     * 補充燃料方式行為
     */
    private RefuelWay refuelWay;

    /**
     * 構造方法中設定兩種行為
     */
    public Car(Brand brand,RefuelWay refuelWay){
        this.brand = brand;
        this.refuelWay = refuelWay;
    }

    /**
     * 展示品牌 委託給品牌具體實現類
     */
    public void getBrand(){
        brand.getBrand();
    }

    /**
     * 補充燃料 委託給補充燃料的具體實現類
     */
    public void refuel(){
        refuelWay.refuel();
    }

    /**
     * 發動
     */
    public void start(){
        System.out.println("發動");
    }

    /**
     * 加速
     */
    public void accele(){
        System.out.println("加速");
    }

    /**
     * 剎車
     */
    public void brake(){
        System.out.println("剎車");
    }

    /**
     * 展示方法 方便我們測試
     */
    public void display(){
        this.start();
        this.accele();
        this.brake();
        this.getBrand();
        this.refuel();
    }
}

測試類

public class Test {
    public static void main(String[] args) {
        // 建立不同的品牌
        Brand bwm = new BWM();
        Brand tesla = new Tesla();
        Brand byd = new BYD();

        // 建立不同的燃料補充方式
        RefuelWay oil = new Oil();
        RefuelWay electricity = new Electricity();

        // 構造 寶馬的汽油車
        System.out.println("------------構造 寶馬的汽油車--------------");
        Car car1 = new Car(bwm,oil);
        car1.display();

        // 構造 寶馬電動車
        System.out.println("------------構造 寶馬電動車--------------");
        Car car2 = new Car(bwm, electricity);
        car2.display();

        // 構造 特斯拉電動車
        System.out.println("------------構造 特斯拉電動車--------------");
        Car car3 = new Car(tesla,electricity);
        car3.display();

        // 構造 比亞迪電動車
        System.out.println("------------構造 比亞迪電動車--------------");
        Car car4 = new Car(byd,electricity);
        car4.display();

        // 構造 比亞迪汽油車
        System.out.println("------------構造 比亞迪汽油車--------------");
        Car car5 = new Car(byd,oil);
        car5.display();
    }
}

輸出:

------------構造 寶馬的汽油車--------------
發動
加速
剎車
寶馬
加油
------------構造 寶馬電動車--------------
發動
加速
剎車
寶馬
充電
------------構造 特斯拉電動車--------------
發動
加速
剎車
特斯拉
充電
------------構造 比亞迪電動車--------------
發動
加速
剎車
比亞迪
充電
------------構造 比亞迪汽油車--------------
發動
加速
剎車
比亞

對於一開始使用的繼承,如果父類後期發生變化,那麼對於子類的影響是非常大的,同時繼承也非常的不靈活。而使用組合時,如果後期行為發生了變化,我們只需要切換這個行為的實現類即可,而Car中使用的時實現這個行為的介面,對其子類不會產生影響,如果需要新增新的行為,只需要新增一個新的行為介面,並將其委託給具體的實現類即可。

由此我們可以得出:實際開發中,我們應當多用組合,少用繼承。

這裡對展示品牌和補充燃料方式行為的封裝就是使用的策略模式,再次閱讀策略模式的定義,應當容易理解了。