設計模式專題 - 策略模式

一. 設計模式概述

1.爲什麼使用設計模式?

使用設計模式可以重構整體架構代碼、提交代碼複用性、擴展性、減少代碼冗餘問題。Java高級工程師必備的技能!

2.設計模式六大原則:

① 開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。

② 里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

③ 依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。

④ 接口隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

⑤ 迪米特法則(最少知道原則)(Demeter Principle)

爲什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。

⑥ 合成複用原則(Composite Reuse Principle)

原則是儘量使用合成/聚合的方式,而不是使用繼承。

3. 什麼是策略模式?

策略模式是對算法的包裝,是把使用算法的責任和算法本身分割開來,委派給不同的對象管理,最終可以解決多重if判斷問題。

①.環境(Context)角色:持有一個Strategy的引用。

②.抽象策略(Strategy)角色:這是一個抽象角色,通常由一個接口或抽象類實現。此角色給出所有的具體策略類所需的接口

③.具體策略(ConcreteStrategy)角色:包裝了相關的算法或行爲,即實現類。

爲什麼叫做策略模式?  每個if判斷都可以理解爲就是一個策略。

4.策略模式優缺點

優點:算法可以自由切換(高層屏蔽算法,角色自由切換),避免使用多重條件判斷(如果算法過多就會出現很多種相同的判斷,很難維護),擴展性好(可自由添加取消算法 而不影響整個功能)

缺點:策略類數量增多(每一個策略類複用性很小,如果需要增加算法,就只能新增類),所有的策略類都需要對外暴露(使用的人必須瞭解使用策略,這個就需要其它模式來補充,比如工廠模式、代理模式)

二. 策略模式案例分析

聚合支付平臺:搭建聚合支付平臺的時候,這時候需要對接很多第三方支付接口,比如支付寶、微信支付、小米支付等,通過傳統if代碼判斷的,後期的維護性非常差!

這時候可以通過策略模式解決多重if判斷問題。

架構圖:

1. 基於枚舉+工廠方式實現策略模式

maven依賴:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
</parent>
<dependencies>
    <!-- sprinboot web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    <!-- mysql 依賴 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

① 創建抽象策略(接口,共同行爲)

package com.example.strategy;
public interface PayStrategy {
    // 共同算法實現骨架
    String toPay();
}

② 創建策略實現

package com.example.strategy.impl;
import com.example.strategy.PayStrategy;
import org.springframework.stereotype.Component;
@Component
public class AliPayStrategy implements PayStrategy {
    public String toPay() {
        return "調用支付寶支付接口";
    }
}
package com.example.strategy.impl;
import com.example.strategy.PayStrategy;
public class WxPayStrategy implements PayStrategy {
    public String toPay() {
        return "調用微信支付接口";
    }
}

③ 定義枚舉(存放每個測了測實現 - class路徑)

package com.example.context;
public enum PayEnum {
    // 支付寶支付
    ALI_PAY("com.example.strategy.impl.AliPayStrategy"),
    // 微信支付
    WX_PAY("com.example.strategy.impl.WxPayStrategy");
    PayEnum(String className) {
        this.setClassName(className);
    }
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    // class完整地址
    private String className;
}

④ 定義策略工廠

package com.example.factory;
import com.example.context.PayEnum;
import com.example.strategy.PayStrategy;
public class StrategyFactory {
    //使用策略工廠獲取具體策略實現
    public static PayStrategy getPayStrategy(String strategyType) {
        try {
            // 1.獲取枚舉中className
            String className = PayEnum.valueOf(strategyType).getClassName();
            // 2.使用java反射技術初始化類
            return (PayStrategy) Class.forName(className).newInstance();
        } catch (Exception e) {
            return null;
        }
    }
}

⑤ 定義上下文

package com.example.context;
import com.example.factory.StrategyFactory;
import com.example.strategy.PayStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class PayContext {
    public String toPay(String payCode){
        // 1.驗證參數
        if(StringUtils.isEmpty(payCode)){
            return  "payCode不能爲空!";
        }
        // 2.使用策略工廠獲取具體的策略實現
        PayStrategy payStrategy = StrategyFactory.getPayStrategy(payCode);
        if(payStrategy==null){
            return  "沒有找到具體策略的實現...";
        }
        return payStrategy.toPay();
    }
}

⑥ 編寫controller進行測試

package com.example.controller;
import com.example.context.PayContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
    @Autowired
    private PayContext payContext;
    @RequestMapping("/toPay")
    public String toPay(String payCode){
        return payContext.toPay(payCode);
    }
}

      

2. 基於數據庫實現策略模式

枚舉方式,具體的策略實現都沒有交給Spring管理,所以現在想全交給Spring進行管理,棄用工廠

準備工作:如圖建立一張支付渠道表,關鍵爲channel_id和strategy_bean_id,即和上面枚舉的key-value相似

    

具體實現:

① 首先需要把策略實現注入到spring容器中

  

② 此時直接棄用枚舉和工廠,直接修改context上下文代碼:

package com.example.context;
import com.example.dao.PaymentDao;
import com.example.model.PaymentDo;
import com.example.strategy.PayStrategy;
import com.example.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class PayContext {
    @Autowired
    private PaymentDao paymentDao;
    public String toPay(String payCode){
        // 1.驗證參數
        if(StringUtils.isEmpty(payCode)){
            return  "payCode不能爲空!";
        }
        // 1.查詢數據庫獲取具體策略實現
        PaymentDo paymentDo = paymentDao.getPaymentChannel(payCode);
        if (paymentDo == null) {
            return "沒有查詢到支付渠道";
        }
        System.out.println(paymentDo);
        // 獲取spring注入的bean的id
        String strategyBeanId = paymentDo.getStrategyBeanId();
        if (StringUtils.isEmpty(strategyBeanId)) {
            return "數據庫沒有配置strategyBeanId";
        }
        PayStrategy payStrategy = SpringUtils.getBean(strategyBeanId, PayStrategy.class);
        return payStrategy.toPay();
    }
}

③ 編寫yml配置

server:
  port: 8081
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
mybatis:
  configuration:
    mapUnderscoreToCamelCase: true

測試一下,和基於枚舉+工廠效果一樣:

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