工廠模式和策略模式的綜合使用

目錄

一、簡單的工廠模式瞭解與使用

(一)基本概念理解

(二)簡單工廠模式的認識和對應角色的分析

基本認識

角色理解

(三)使用場景和典型應用

二、簡單的策略模式瞭解與使用

(一)基本概念理解

(二)策略模式認識和對應角色的分析

基本認識

角色理解

(三)使用場景和典型應用

三、工廠模式和策略模式的綜合使用

(一)應用背景介紹

(二)設計策略類

1.定義一個計算應付價格接口

2.用戶是專屬會員對應策略類

3.用戶是超級會員對應策略類

4.用戶是普通會員對應策略類

(三)設計對應的簡單工廠模式

(四)知識補充:Spring Bean的註冊

(五)具體實現應用測試如下

1.具體測試代碼

2.對應結果展示

參考書籍、文獻和資料


其實在很多的開發設計中,將工廠模式和策略模式的綜合使用的案例是很多的,而且解決的實際問題也一樣很多,本次對基本的簡單工廠模式和策略模式做簡單介紹,重點放在兩者結合後的具體應用上做分析和講解。

一、簡單的工廠模式瞭解與使用

(一)基本概念理解

建立一個工廠,能輕鬆方便地構造對象實例,而不必關心構造對象實例的細節和複雜過程,這就是簡單工廠的主要功能。

比方如下圖:客戶需要一輛寶馬,具體的簡單工廠會根據用戶的實際需求去生產對應型號的寶馬,最後返回客戶需要的寶馬產品。

(二)簡單工廠模式的認識和對應角色的分析

基本認識

簡單工廠模式(Simple Factory Pattern)需要定義一個工廠類,它可以根據參數的不同返回不同類的實例,被創建的實例通常都具有共同的父類。因爲在簡單工廠模式中用於創建實例的方法是靜態(static)方法,因此簡單工廠模式又被稱爲靜態工廠方法(Static Factory Method)模式,它屬於類創建型模式,但不屬於GOF23種設計模式。

角色理解

  • Factory(工廠角色):工廠角色即工廠類,它是簡單工廠模式的核心,負責實現創建所有產品實例的內部邏輯;工廠類可以被外界直接調用,創建所需的產品對象;在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回類型爲抽象產品類型Product
  • Product(抽象產品角色):它是工廠類所創建的所有對象的父類,封裝了各種產品對象的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因爲所有創建的具體產品對象都是其子類對象。
  • ConcreteProduct(具體產品角色):它是簡單工廠模式的創建目標,所有被創建的對象都充當這個角色的某個具體類的實例。每一個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中聲明的抽象方法

在簡單工廠模式中,客戶端通過工廠類來創建一個產品類的實例,而無須直接使用new關鍵字來創建對象,它是工廠模式家族中最簡單的一員。

(三)使用場景和典型應用

  • 工廠類負責創建的對象比較少,由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。
  • 客戶端只知道傳入工廠類的參數,對於如何創建對象並不關心。
  • 典型應用:Calendar 類獲取日曆類對象、JDBC 獲取數據庫連接、Logback 中的 LoggerFactory 獲取 Logger 對象

二、簡單的策略模式瞭解與使用

(一)基本概念理解

主要指對象有某個行爲,但是在不同的場景中,該行爲有不同的實現算法。

比如每個人都要“交個人所得稅”,但是“在美國交個人所得稅”和“在中國交個人所得稅”就有不同的算稅方法。

在實際的代碼中,外賣平臺上的某家店鋪爲了促銷,設置了多種會員優惠,其中包含超級會員折扣8折、普通會員折扣9折和普通用戶沒有折扣三種,也就是針對不同的會員有不同的優惠力度。

(二)策略模式認識和對應角色的分析

基本認識

策略模式是行爲模式之一,它對一系列的算法加以封裝,爲所有算法定義一個抽象的算法接口,並通過繼承該抽象算法接口對所有的算法加以封裝和實現,具體的算法選擇交由客戶端決定(策略)。Strategy 模式主要用來平滑地處理算法的切換。

角色理解

  • Strategy : 策略(算法)抽象。
  • ConcreteStrategy :各種策略(算法) 的具體實現
  • Contenxt :策略的外部封裝類,或者說策略的容器類。根據不同策略執行不同的行爲,策略由外部環境決定。

(三)使用場景和典型應用

  • 策略模式的等級結構定義了一個算法或行爲族,恰當使用繼承可以把公共的代碼移到父類裏面,從而避免重複的代碼。
  • 策略模式提供了可以替換繼承關係的辦法。
  • 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護。它把採取哪一種算法或採取哪一種行爲的邏輯與算法或行爲的邏輯混合在一起。統統列在一個多重轉移語句裏面,比使用繼承的辦法還要原始和落後。

三、工廠模式和策略模式的綜合使用

(一)應用背景介紹

假設我們要做一個外賣平臺,有這樣的需求:

  • 外賣平臺上的某家店鋪爲了促銷,設置了多種會員優惠,其中包含超級會員折扣8折、普通會員折扣9折和普通用戶沒有折扣三種。
  • 希望用戶在付款的時候,根據用戶的會員等級,就可以知道用戶符合哪種折扣策略,進而進行打折,計算出應付金額。
  • 隨着業務發展,新的需求要求專屬會員要在店鋪下單金額大於30元的時候纔可以享受優惠。
  • 接着,又有一個變態的需求,如果用戶的超級會員已經到期了,並且到期時間在一週內,那麼就對用戶的單筆訂單按照超級會員進行折扣,並在收銀臺進行強提醒,引導用戶再次開通會員,而且折扣只進行一次。

(二)設計策略類

根據要求,用戶具有會員等級,對應的會員等級在付款的時候享受對應的折扣策略,而後面是具體業務在不同變動時新增的內容,可以在具體的策略中去單獨操作或在外圍確定用戶身份後進行基本的處理。

總之,從基本操作上來看,付款的行爲是必然存在的,不同的用戶在對應不同的變動下支付的具體策略有所不同,所以,可以確定一個基本的接口來表達具體要付款的行爲,不同用戶對具體等級的實現策略放在對應的實現類中做處理:

1.定義一個計算應付價格接口

/**
 * @author yanfengzhang
 */
public interface UserPayService {

    /**
     * 功能描述:計算應付價格
     * @author yanfengzhang
     * @date 2019-12-31 18:09
     * @param orderPrice BigDecimal
     * @return BigDecimal
    */
    BigDecimal quote(BigDecimal orderPrice);
}

2.用戶是專屬會員對應策略類

/**
 * 描述:用戶是專屬會員---訂單金額大於30元,7折價格/////否則9折價格
 *
 * @author yanfengzhang
 * @date 2019-12-31 18:11
 */
@Service
public class ParticularlyVipPayServiceImpl implements UserPayService, InitializingBean {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        int payPrice = orderPrice.intValue();
        if (payPrice > 30) {
            return new BigDecimal(payPrice * 0.7);
        }
        return new BigDecimal(payPrice * 0.9);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        UserPayServiceStrategyFactory.register("ParticularlyVip", this);
    }
}

3.用戶是超級會員對應策略類

/**
 * 描述:用戶是超級會員---8折價格
 *
 * @author yanfengzhang
 * @date 2019-12-31 18:13
 */
@Service
public class SuperVipPayServiceImpl implements UserPayService , InitializingBean {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        int payPrice = orderPrice.intValue();
        return new BigDecimal(payPrice * 0.8);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        UserPayServiceStrategyFactory.register("SuperVip",this);
    }
}

4.用戶是普通會員對應策略類

/**
 * 描述:用戶是普通會員
 * 情況1:該用戶超級會員剛過期並且尚未使用過臨時折扣-->臨時折扣使用次數更新-->8折價格
 * 情況2:非以上情況-->9折價格
 *
 * @author yanfengzhang
 * @date 2019-12-31 18:15
 */
@Service
public class VipPayServiceImpl implements UserPayService, InitializingBean {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        int payPrice = orderPrice.intValue();
        /*該用戶超級會員剛過期並且尚未使用過臨時折扣*/
        if (conditions()) {
            /*臨時折扣使用次數更新*/
            updateSomething();
            return new BigDecimal(payPrice * 0.8);
        }
        return new BigDecimal(payPrice * 0.9);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        UserPayServiceStrategyFactory.register("Vip", this);
    }

    /**
     * 功能描述:滿足一定的條件
     *
     * @author yanfengzhang
     * @date 2020-01-02 09:11
     */
    private boolean conditions() {
        return true;
    }

    private void updateSomething() {

    }
}

(三)設計對應的簡單工廠模式

爲了方便我們從Spring中獲取UserPayService的各個策略類,我們創建一個工廠類來實現針對不同的用戶實現對應的支付策略,具體如下:

/**
 * 描述:獲取UserPayService的各個策略類
 * UserPayServiceStrategyFactory中定義了一個Map,用來保存所有的策略類的實例,並提供一個getByUserType方法,可以根據類型直接獲取對應的類的實例。
 *
 * @author yanfengzhang
 * @date 2019-12-31 18:28
 */
public class UserPayServiceStrategyFactory {
    private static Map<String, UserPayService> services = new ConcurrentHashMap<String,UserPayService>();

    public  static UserPayService getByUserType(String type){
        return services.get(type);
    }

    public static void register(String userType,UserPayService userPayService){
        Assert.notNull(userType,"userType can't be null");
        services.put(userType,userPayService);
    }
}

(四)知識補充:Spring Bean的註冊

UserPayServiceStrategyFactory提供了register方法,用來註冊策略服務的。各個策略類調用register方法,把Spring通過IOC創建出來的Bean註冊進去就行了。這種需求,可以借用Spring種提供的InitializingBean接口,這個接口爲Bean提供了屬性初始化後的處理方法,它只包括afterPropertiesSet方法,凡是繼承該接口的類,在bean的屬性初始化後都會執行該方法。

只需要每一個策略服務的實現類都實現InitializingBean接口,並實現其afterPropertiesSet方法,在這個方法中調用UserPayServiceStrategyFactory.register即可。

這樣,在Spring初始化的時候,當創建VipPayService、SuperVipPayService和ParticularlyVipPayService的時候,會在Bean的屬性初始化之後,把這個Bean註冊到UserPayServiceStrategyFactory中。

(五)具體實現應用測試如下

1.具體測試代碼

/**
 * 描述:通過策略模式、工廠模式以及Spring的InitializingBean,提升了代碼的可讀性以及可維護性,徹底消滅了一坨if-else
 * 注:1.這裏使用的並不是嚴格意義上面的策略模式和工廠模式。
 *
 * @author yanfengzhang
 * @date 2019-12-31 18:21
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DeleteIfElseSkill {
    @Test
    public void testDeleteIfElseSkill() {
        User user1 = new User();
        user1.setVipType("ParticularlyVip");
        user1.setOrderPrice(new BigDecimal("100"));

        User user2 = new User();
        user2.setVipType("SuperVip");
        user2.setOrderPrice(new BigDecimal("100"));

        User user3 = new User();
        user3.setVipType("Vip");
        user3.setOrderPrice(new BigDecimal("100"));


        BigDecimal payPrice1 = UserPayServiceStrategyFactory.getByUserType(user1.getVipType()).quote(user1.getOrderPrice());
        System.out.println("用戶爲專屬會員,並且訂單金額爲50,按會員優惠最後應支付:" + payPrice1);

        BigDecimal payPrice2 = UserPayServiceStrategyFactory.getByUserType(user2.getVipType()).quote(user2.getOrderPrice());
        System.out.println("用戶爲超級會員,並且訂單金額爲50,按會員優惠最後應支付:" + payPrice2);

        BigDecimal payPrice3 = UserPayServiceStrategyFactory.getByUserType(user3.getVipType()).quote(user3.getOrderPrice());
        System.out.println("用戶爲普通會員,並且訂單金額爲50,按會員優惠最後應支付:" + payPrice3);
    }

    @Data
    private static class User {
        private String uid;
        private String vipType;
        private BigDecimal orderPrice;
    }
}

2.對應結果展示

用戶爲專屬會員,並且訂單金額爲50,按會員優惠最後應支付:70
用戶爲超級會員,並且訂單金額爲50,按會員優惠最後應支付:80
用戶爲普通會員,並且訂單金額爲50,按會員優惠最後應支付:80

 

參考書籍、文獻和資料

1.https://blog.csdn.net/jason0539/article/details/23020989

2.https://blog.csdn.net/wwwdc1012/article/details/82504040

3.https://blog.csdn.net/lzb348110175/article/details/91633620

4.https://blog.csdn.net/Crazy_Cw/article/details/106818217

5.https://mp.weixin.qq.com/s/RG-h7r69JyKUlBZylJJIFQ

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章