大話設計模式筆記(二)の策略模式

舉個栗子

問題描述

商場收銀軟件,營業員根據客戶所購買的商品單價和數量,向客戶收費。算法

簡單實現

/**
 * 普通實現
 * Created by callmeDevil on 2019/6/1.
 */
public class NormalTest {

    public static void main(String[] args) {
        double price = 10;
        double num = 5;
        System.out.println(String.format("單價:%s 元", price));
        System.out.println(String.format("數量:%s 個", num));
        System.out.println(String.format("總價:%s 元", calculateTotal(price, num)));
    }

    /**
     * 計算總價
     *
     * @param price 單價
     * @param num   數量
     * @return
     */
    private static double calculateTotal(double price, double num) {
        return price * num;
    }

}

問題2

商品搞促銷,打八折,也可能打七折,甚至五折。編程

數組實現

/**
 * 普通實現2
 * Created by callmeDevil on 2019/6/1.
 */
public class NormalTest2 {

    public static void main(String[] args) {
        double price = 10;
        double num = 5;
        String[] discounts = {"正常收費", "打八折", "打七折", "打五折"};
        System.out.println(String.format("單價:%s 元", price));
        System.out.println(String.format("數量:%s 個", num));
        System.out.println(String.format("折扣:%s ", discounts[1]));
        System.out.println(String.format("總價:%s 元", calculateTotal(price, num, 1)));
    }

    /**
     * 計算總價
     *
     * @param price    單價
     * @param num      數量
     * @param discount 折扣
     * @return
     */
    private static double calculateTotal(double price, double num, int discount) {
        double total = 0L;
        switch (discount) {
            case 0:
                total = price * num;
                break;
            case 1:
                total = price * num * 0.8;
                break;
            case 2:
                total = price * num * 0.7;
                break;
            case 3:
                total = price * num * 0.5;
                break;
            default:
                break;
        }
        return total;
    }

}

上述方式存在問題

有不少重複代碼,就switch語句來講,若是計算方式比較複雜,那麼這裏就會顯得很是冗餘,必須考慮重構,抽出共性代碼。並且若是須要打其餘折扣,修改的地方也不少。數組

使用簡單工廠模式

面向對象的編程,並非類越多越好,類的劃分是爲了封裝,但分類的基礎是抽象,具備相同屬性和功能的對象的抽象集合纔是類。ide

/**
 * 現金收費抽象類
 * Created by callmeDevil on 2019/6/1.
 */
public abstract class CashSuper {
    /**
     * 收取現金
     *
     * @param money 原價
     * @return 當前價
     */
    public abstract double acceptCash(double money);
}
/**
 * 正常收費子類
 * Created by callmeDevil on 2019/6/1.
 */
public class CashNormal extends CashSuper {
    @Override
    public double acceptCash(double money) {
        // 正常收費,原價返回
        return money;
    }
}
/**
 * 返利收費子類
 * Created by callmeDevil on 2019/6/1.
 */
public class CashReturn extends CashSuper{

    // 返利條件
    private double moneyCondition = 0;
    // 返利值
    private double moneyReturn = 0;

    // 返利收費,初始化時必須輸入返利條件和返利值,好比滿300返100,
    // 則moneyCondition 爲300,moneyReturn 爲100
    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double money) {
        double result = money;
        if (money >= moneyCondition) {
            // 若大於返利條件,則須要減去返利值
            result = money - Math.floor(money / moneyCondition) * moneyReturn;
        }
        return result;
    }

}
/**
 * 打折收費子類
 * Created by callmeDevil on 2019/6/1.
 */
public class CashRebate extends CashSuper{

    // 折扣率
    private double moneyRebate = 1;

    public CashRebate(double moneyRebate) {
        // 打折收費,初始化時,必須輸入折扣率,如打八折,就是0.8
        this.moneyRebate = moneyRebate;
    }

    @Override
    public double acceptCash(double money) {
        return money * moneyRebate;
    }

}
/**
 * 現金收費工廠類
 * Created by callmeDevil on 2019/6/1.
 */
public class CashFactory {

    /**
     * 建立現金收取工廠實例
     *
     * @param type 收費類型
     * @return
     */
    public static CashSuper createCashAccept(String type) {
        CashSuper cs = null;
        switch (type) {
            case "正常收費":
                cs = new CashNormal();
                break;
            case "滿300減100":
                cs = new CashReturn(300, 100);
                break;
            case "打8折":
                cs = new CashRebate(0.8);
                break;
            default:
                break;
        }
        return cs;
    }

}
/**
 * 現金收費測試
 * Created by callmeDevil on 2019/6/1.
 */
public class CashTest {

    public static void main(String[] args) {
        double price = 400;
        double num = 3;
        System.out.println(String.format("單價:%s 元,數量:%s 個", price, num));

        String type = "正常收費";
        CashSuper cashSuper = CashFactory.createCashAccept(type);
        double total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));

        type = "滿300減100";
        cashSuper = CashFactory.createCashAccept(type);
        total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));

        type = "打8折";
        cashSuper = CashFactory.createCashAccept(type);
        total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));
    }

}

輸出結果單元測試

單價:400.0 元,數量:3.0 個
折扣:正常收費;總價:1200.0 元
折扣:滿300減100;總價:900.0 元
折扣:打8折;總價:960.0 元

仍然存在的缺點

簡單工廠模式雖然也可以解決問題2,但這個模式只是解決對象的建立問題,並且因爲工廠自己包括了全部的收費模式,商場是可能常常性的更改打折額度和返利額度,每次維護或擴展收費方式都要改動這個工廠,以至代碼須要從新編譯部署,這是很糟糕的,因此不是最好的解決辦法。測試

策略模式

概念

定義了算法家族,分別封裝起來,讓他們之間能夠互相替換,此模式讓算法的變化,不回影響到使用算法的客戶。this

UML圖

代碼實現

其實上面的簡單工廠模式實現方式裏面的CashSuper、CashNormal、CashRebate、CashReturn都不須要更改,只須要增長一個CashContext類,同時修改下客戶端就能夠了。code

/**
 * 現金上下文
 * Created by callmeDevil on 2019/6/1.
 */
public class CashContext {

    private CashSuper cs = null;

    public CashContext(String type) {
        switch (type) {
            case "正常收費":
                cs = new CashNormal();
                break;
            case "滿300減100":
                cs = new CashReturn(300, 100);
                break;
            case "打8折":
                cs = new CashRebate(0.8);
                break;
            default:
                break;
        }
    }

    public double getResult(double money) {
        return cs.acceptCash(money);
    }

}
/**
 * 策略模式測試
 * Created by callmeDevil on 2019/6/1.
 */
public class ContextTest {

    public static void main(String[] args) {
        double price = 400;
        double num = 3;
        System.out.println(String.format("單價:%s 元,數量:%s 個", price, num));

        String type = "正常收費";
        CashContext cashContext = new CashContext(type);
        double total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));

        type = "滿300減100";
        cashContext = new CashContext(type);
        total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));

        type = "打8折";
        cashContext = new CashContext(type);
        total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;總價:%s 元", type, total));
    }

}

須要注意的是

策略模式測試類中的代碼與簡單工廠的很是類似,由於這裏將策略模式與簡單工廠模式作告終合,所以比較難以判斷策略模式的好處到底在哪。若是仔細分析一下會發現,只用簡單工廠的測試類中,也就是客戶端代碼耦合了CashSuperCashFactory兩個類,而使用了策略模式的客戶端只涉及到了CashContext一個類,將客戶端與具體算法的實現進行了解耦,這樣若是商場須要變動促銷折扣時,除了變動具體的折扣實現類,只須要更改CashContext便可,客戶端徹底不用作任何更改,這就是策略模式帶來的最大好處。orm

總結

  • 策略模式是一種定義一系列算法的方法,從概念上看,全部這些算法完成的都是相同的工做,只是實現不一樣,它能夠以相同的方式調用全部的算法,減小了各類算法類與使用算法類之間的耦合。
  • 策略模式的Strategy類層次爲Context定義了一系列的可供重用的算法或行爲。
  • 策略模式另外一個優勢就是簡化了單元測試,由於每一個算法都有本身的類,能夠經過本身的接口單獨測試。
  • 當不一樣的行爲堆砌在一個類中,就很難避免使用條件語句來選擇合適的行爲,將這些行爲封裝在一個個獨立的Strategy類中,能夠在使用這些行爲的類中消除條件語句。
  • 策略模式封裝了算法,但在實踐中,咱們發現能夠用它來封裝幾乎任何類型的規則,只要在分析過程當中聽到須要在不一樣時間應用不一樣的業務規則,就能夠考慮使用策略模式處理這種變化的可能性。
  • 在基本的策略模式中,選擇所用具體實現的職責由客戶端對象承擔,並轉給策略模式的Context對象。
  • 最後不得不說的是,每增長一種算法,都免不了修改CashContext中的switch分支,這是沒辦法的,由於任何需求的變動都須要成本
相關文章
相關標籤/搜索