重學 Java 設計模式:實戰策略模式「模擬多種營銷型別優惠券,折扣金額計算策略場景」

小傅哥發表於2020-07-06


作者:小傅哥
部落格:https://bugstack.cn - 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!?

一、前言

文無第一,武無第二

不同方向但同樣努力的人,都有自身的價值和亮點,也都是可以互相學習的。不要太過於用自己手裡的矛去攻擊別人的盾?,哪怕一時爭辯過了也多半可能是你被安放的角色不同。取別人之強補自己之弱,矛與盾的結合可能就是坦克。

能把複雜的知識講的簡單很重要

在學習的過程中我們看過很多資料、視訊、文件等,因為現在資料視訊都較多所以往往一個知識點會有多種多樣的視訊形式講解。除了推廣營銷以外,確實有很多人的視訊講解非常優秀,例如李永樂老師的短視訊課,可以在一個黑板上把那麼複雜的知識,講解的那麼容易理解,那麼透徹。而我們學習程式設計的人也是,不只是要學會把知識點講明白,也要寫明白。

?提升自己的眼界交往更多同好

有時候圈子很重要,就像上學期間大家都會發現班裡有這樣一類學生?‍?不怎麼聽課,但是就是學習好。那假如讓他回家呆著,不能在課堂裡呢?類似的圈子還有;圖書館、網咖、車友群、技術群等等,都可以給你帶來同類愛好的人所分享出來的技能或者大家一起烘托出的氛圍幫你成長。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回覆原始碼下載獲取(開啟獲取的連結,找到序號18)
工程 描述
itstack-demo-design-20-01 使用一坨程式碼實現業務需求
itstack-demo-design-20-02 通過設計模式優化改造程式碼,產生對比性從而學習

三、策略模式介紹

策略模式,圖片來自 refactoringguru.cn

策略模式是一種行為模式,也是替代大量ifelse的利器。它所能幫你解決的是場景,一般是具有同類可替代的行為邏輯演算法場景。比如;不同型別的交易方式(信用卡、支付寶、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花演算法、Leaf演算法)等,都可以使用策略模式進行行為包裝,供給外部使用。

諸葛亮錦囊妙計

策略模式也有點像三國演義中諸葛亮給劉關張的錦囊;

  • 第一個錦囊:見喬國老,並把劉備娶親的事情du搞得東吳人盡皆知。
  • 第二個錦囊:用謊言(曹操打荊州)騙泡在溫柔鄉里的劉備回去。
  • 第三個錦囊:讓孫夫人擺平東吳的追兵,她是孫權妹妹,東吳將領懼她三分。

四、案例場景模擬

場景模擬;商品支付使用營銷優惠券

在本案例中我們模擬在購買商品時候使用的各種型別優惠券(滿減、直減、折扣、n元購)

這個場景幾乎也是大家的一個日常購物省錢渠道,購買商品的時候都希望找一些優惠券,讓購買的商品更加實惠。而且到了大促的時候就會有更多的優惠券需要計算那些商品一起購買更加優惠!!!

這樣的場景有時候使用者用起來還是蠻爽的,但是最初這樣功能的設定以及產品的不斷迭代,對於程式設計師?‍?‍開發還是不太容易的。因為這裡包括了很多的規則和優惠邏輯,所以我們模擬其中的一個計算優惠的方式,使用策略模式來實現。

五、用一坨坨程式碼實現

這裡我們先使用最粗暴的方式來實現功能

對於優惠券的設計最初可能非常簡單,就是一個金額的抵扣,也沒有現在這麼多種型別。所以如果沒有這樣場景的經驗話,往往設計上也是非常簡單的。但隨著產品功能的不斷迭代,如果程式最初設計的不具備很好的擴充套件性,那麼往後就會越來越混亂。

1. 工程結構

itstack-demo-design-20-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── CouponDiscountService.java
  • 一坨坨工程的結構很簡單,也是最直接的程式導向開發方式。

2. 程式碼實現

/**
 * 部落格:https://bugstack.cn - 沉澱、分享、成長,讓自己和他人都能有所收穫!
 * 公眾號:bugstack蟲洞棧
 * Create by 小傅哥(fustack) @2020
 * 優惠券折扣計算介面
 * <p>
 * 優惠券型別;
 * 1. 直減券
 * 2. 滿減券
 * 3. 折扣券
 * 4. n元購
 */
public class CouponDiscountService {

    public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
        // 1. 直減券
        if (1 == type) {
            return skuPrice - typeContent;
        }
        // 2. 滿減券
        if (2 == type) {
            if (skuPrice < typeExt) return skuPrice;
            return skuPrice - typeContent;
        }
        // 3. 折扣券
        if (3 == type) {
            return skuPrice * typeContent;
        }
        // 4. n元購
        if (4 == type) {
            return typeContent;
        }
        return 0D;
    }

}
  • 以上是不同型別的優惠券計算折扣後的實際金額。
  • 入參包括;優惠券型別、優惠券金額、商品金額,因為有些優惠券是滿多少減少多少,所以增加了typeExt型別。這也是方法的不好擴充套件性問題。
  • 最後是整個的方法體中對優惠券抵扣金額的實現,最開始可能是一個最簡單的優惠券,後面隨著產品功能的增加,不斷的擴充套件if語句。實際的程式碼可能要比這個多很多。

六、策略模式重構程式碼

接下來使用策略模式來進行程式碼優化,也算是一次很小的重構。

與上面面向流程式的開發這裡會使用設計模式,優惠程式碼結構,增強整體的擴充套件性。

1. 工程結構

itstack-demo-design-20-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── event
                │    └── MJCouponDiscount.java
                │    └── NYGCouponDiscount.java
                │    └── ZJCouponDiscount.java
                │    └── ZKCouponDiscount.java
                ├── Context.java
                └── ICouponDiscount.java

策略模式模型結構

策略模式模型結構

  • 整體的結構模式並不複雜,主要體現的不同型別的優惠券在計算優惠券方式的不同計算策略。
  • 這裡包括一個藉口類(ICouponDiscount)以及四種優惠券型別的實現方式。
  • 最後提供了策略模式的上下文控制類處理,整體的策略服務。

2. 程式碼實現

2.1 優惠券介面

public interface ICouponDiscount<T> {

    /**
     * 優惠券金額計算
     * @param couponInfo 券折扣資訊;直減、滿減、折扣、N元購
     * @param skuPrice   sku金額
     * @return           優惠後金額
     */
    BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);

}
  • 定義了優惠券折扣介面,也增加了泛型用於不同型別的介面可以傳遞不同的型別引數。
  • 介面中包括商品金額以及出參返回最終折扣後的金額,這裡在實際開發中會比現在的介面引數多一些,但核心邏輯是這些。

2.2 優惠券介面實現

滿減

public class MJCouponDiscount implements ICouponDiscount<Map<String,String>>  {

    /**
     * 滿減計算
     * 1. 判斷滿足x元后-n元,否則不減
     * 2. 最低支付金額1元
     */
    public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {
        String x = couponInfo.get("x");
        String o = couponInfo.get("n");

        // 小於商品金額條件的,直接返回商品原價
        if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice;
        // 減去優惠金額判斷
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;

        return discountAmount;
    }
}

直減

public class ZJCouponDiscount implements ICouponDiscount<Double>  {

    /**
     * 直減計算
     * 1. 使用商品價格減去優惠價格
     * 2. 最低支付金額1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

折扣

public class ZKCouponDiscount implements ICouponDiscount<Double> {


    /**
     * 折扣計算
     * 1. 使用商品價格乘以折扣比例,為最後支付金額
     * 2. 保留兩位小數
     * 3. 最低支付金額1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

N元購

public class NYGCouponDiscount implements ICouponDiscount<Double> {

    /**
     * n元購購買
     * 1. 無論原價多少錢都固定金額購買
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        return new BigDecimal(couponInfo);
    }

}
  • 以上是四種不同型別的優惠券計算折扣金額的策略方式,可以從程式碼中看到每一種優惠方式的優惠金額。

2.3 策略控制類

public class Context<T> {

    private ICouponDiscount<T> couponDiscount;

    public Context(ICouponDiscount<T> couponDiscount) {
        this.couponDiscount = couponDiscount;
    }

    public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
        return couponDiscount.discountAmount(couponInfo, skuPrice);
    }

}
  • 策略模式的控制類主要是外部可以傳遞不同的策略實現,在通過統一的方法執行優惠策略計算。
  • 另外這裡也可以包裝成map結構,讓外部只需要對應的泛型型別即可使用相應的服務。

3. 測試驗證

3.1 編寫測試類(直減優惠)

@Test
public void test_zj() {
    // 直減;100-10,商品100元
    Context<Double> context = new Context<Double>(new ZJCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
    logger.info("測試結果:直減優惠後金額 {}", discountAmount);
}

測試結果

15:43:22.035 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:直減優惠後金額 90

Process finished with exit code 0

3.2 編寫測試類(滿減優惠)

@Test
public void test_mj() {
    // 滿100減10,商品100元
    Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount());
    Map<String,String> mapReq = new HashMap<String, String>();
    mapReq.put("x","100");
    mapReq.put("n","10");
    BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100));
    logger.info("測試結果:滿減優惠後金額 {}", discountAmount);
}

測試結果

15:43:42.695 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:滿減優惠後金額 90

Process finished with exit code 0

3.3 編寫測試類(折扣優惠)

@Test
public void test_zk() {
    // 折扣9折,商品100元
    Context<Double> context = new Context<Double>(new ZKCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
    logger.info("測試結果:折扣9折後金額 {}", discountAmount);
}

測試結果

15:44:05.602 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:折扣9折後金額 90.00

Process finished with exit code 0

3.4 編寫測試類(n元購優惠)

@Test
public void test_nyg() {
    // n元購;100-10,商品100元
    Context<Double> context = new Context<Double>(new NYGCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100));
    logger.info("測試結果:n元購優惠後金額 {}", discountAmount);

測試結果

15:44:24.700 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:n元購優惠後金額 90

Process finished with exit code 0
  • 以上四組測試分別驗證了不同型別優惠券的優惠策略,測試結果是滿足我們的預期。
  • 這裡四種優惠券最終都是在原價100元上折扣10元,最終支付90元

七、總結

  • 以上的策略模式案例相對來說不併不復雜,主要的邏輯都是體現在關於不同種類優惠券的計算折扣策略上。結構相對來說也比較簡單,在實際的開發中這樣的設計模式也是非常常用的。另外這樣的設計與命令模式、介面卡模式結構相似,但是思路是有差異的。
  • 通過策略設計模式的使用可以把我們方法中的if語句優化掉,大量的if語句使用會讓程式碼難以擴充套件,也不好維護,同時在後期遇到各種問題也很難維護。在使用這樣的設計模式後可以很好的滿足隔離性與和擴充套件性,對於不斷新增的需求也非常方便承接。
  • 策略模式介面卡模式組合模式等,在一些結構上是比較相似的,但是每一個模式是有自己的邏輯特點,在使用的過程中最佳的方式是經過較多的實踐來吸取經驗,為後續的研發設計提供更好的技術輸出。

八、推薦閱讀

相關文章