基於SpringBoot的策略模式demo

愛叨叨的程式狗發表於2020-11-29

策略模式

策略模式是Java 23種設計模式之一,在https://refactoring.guru/design-patterns/網站中這樣對策略模式進行了解釋:

Strategy is a behavioral design pattern that turns a set of behaviors into objects and makes them interchangeable inside original context object.

白話翻譯一下就是:策略模式可以根據上下文物件的不同狀態去執行不同的邏輯(策略實現)。最簡單使用場景是:當程式碼中出現了三重以上的if else判斷,這時程式碼的可讀性會非常差,這時可以使用到策略模式去拯救if else.當然也可以使用switch,但是相較於策略模式,switch的程式碼清晰度還是差了些。

策略模式不同角色

  1. Strategy:抽象策略角色,對演算法、策略的抽象,定義每個演算法、策略所必需的方法,通常為介面。
  2. ConcreteStrategy:具體策略角色,實現抽象策略角色,完成具體的演算法、策略。
  3. Context:上下文環境角色,儲存了ConcreteStrategy,負責呼叫ConcreteStrategy。

SpringBoot下使用策略模式

加減乘除的計算器如果使用傳統的if else的話,至少需要寫三次if和一次else,如果使用switch的話,則在一個方法中堆疊四個case或者三個case一個default,如果每個if條件下的邏輯都特別多的話,程式碼會顯得特別臃腫,在《阿里巴巴開發手冊》中規定:一個方法中程式碼量不能超過80行,如果邏輯判斷都擠在一個方法裡,程式碼量一定會超過80行,ok,你的KPI完了。所以,年輕人,耗子尾汁,還不趕緊學學策略模式。

接下來我將使用加減乘除的一個小案例來寫一個最簡單的策略模式的demo

  • 0、引入pom
       <!-- SpringBoot所必須的依賴-->
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
    
    <!-- demo中使用guava的Maps集合和判空-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>

建議:新建一個strategy包,包下定義策略介面實現包strategyImpl和不同的策略角色,也就是上文中的2

ps:實際專案中傳參可能不是基本型別,而是一個自定義的一個包裝型別(實體類),所以入參判斷可以使用@Validated註解判空,我這裡是int基本型別,所以在判空的時候就使用了Google guava封裝的Preconditions去判空,業務判空一定要提前,不要走到較深的業務中再去判空

1、定義策略介面,也就是上文中的1

CalculationStrategy.java

/**
 * @author Liu-PC
 */
public interface CalculationStrategy {
    /**
     * 策略模式的策略介面定義
     * @param num1 first
     * @param num2 second
     * @return
     */
    int operate(int num1, int num2);

}

AddCalculationStrategyImpl.java

/**
 * @author Liutx
 * @date 2020/11/28 16:05
 * @Description
 */
@Component("add")
public class AddCalculationStrategyImpl implements CalculationStrategy {
    @Override
    public int operate(int num1, int num2) {
        return num1 + num2;
    }
}

這裡的@Component是把當前策略的實現類註冊為一個元件,交由Spring管理。add是策略的名字,策略的名字不能重複,因為我們策略介面通過元件的名字來找到具體的策略角色。

DivisionStrategyImpl.java

/**
 * @author Liutx
 * @date 2020/11/28 16:18
 * @Description
 */
 @Component("Division")
public class DivisionStrategyImpl implements CalculationStrategy {
    @Override
    public int operate(int num1, int num2) {
        return num1 / num2;
    }
}

MultiplicationStrategyImpl.java

@Component("multiple")
public class MultiplicationStrategyImpl implements CalculationStrategy {
    @Override
    public int operate(int num1, int num2) {
        return num1 * num2;
    }
}

SubtractionStrategyImpl.java

@Component("subtract")
public class SubtractionStrategyImpl implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 - num2;
    }
}
  • 3、建立上下文物件去呼叫策略

CalculationContext.java

/**
 * @author Liutx
 * @date 2020/11/28 16:23
 * @Description 策略上下文類,把傳入的引數放到map中,作為策略介面的入參
 */

@Component
@Getter
public class CalculationContext {
    /**
     *  把策略角色(型別)key,和引數value放到Map中
     *  key就是beanName(具體策略實現類中@Component的名字),value就是介面(具體的實現類)
     *  Maps是guava下的封裝型別,實則是靜態的建立了一個HashMap的物件,Maps可以根據key去獲取value物件
     */

    public final Map<String, CalculationStrategy> calculationStrategyMap = Maps.newHashMapWithExpectedSize(4);

    public CalculationContext(Map<String, CalculationStrategy> calculationStrategyMap) {
        this.calculationStrategyMap.clear();
        this.calculationStrategyMap.putAll(calculationStrategyMap);
    }
    
    
    //可以使用@Getter註解代替,這樣寫方便讀者理解在Service層呼叫Context執行策略
    public Map<String, CalculationStrategy> getCalculationStrategyMap() {
        return calculationStrategyMap;
    }
}

同樣需要使用@Component,個人理解策略模式中的Context物件例項就像是一個執行者

以上,策略模式就已經寫完了,下面是Controller中呼叫Service,Service中的Context"執行者"通過@Autowired注入的方式去獲得Context物件,物件根據不同的策略角色去執行不同的策略實現。

CalculationService.java

/**
 * @author Liutx
 * @date 2020/11/28 16:03
 * @Description Service裡執行,相當於寫在ServiceImpl裡的業務邏輯,可以在Controller裡呼叫
 * 上下文環境角色,儲存了ConcreteStrategy,負責呼叫ConcreteStrategy,所以就使用Context物件去執行策略
 * 策略的不同實現類,就相當於if else中不同的邏輯程式碼
 * 本demo使用加減乘除代替不同的策略邏輯
 */

@Service
public class CalculationService {

    @Autowired
    private CalculationContext calculationContext;

    public int operateByStrategy(String strategy, int num1, int num2) {
        //獲取入參,根據不同的引數型別去執行不同的策略,Context的get方法是在這個地方用到的,operate方法就是一開始定義的策略介面
        return calculationContext.getCalculationStrategyMap().get(strategy).operate(num1, num2);
    }
}

TestController.class

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private CalculationService calculationService;

    @GetMapping("calculation")
    public int testCalculation(String operation, int num1, int num2) {
        //省略引數判空
        return condition = calculationService.operateByStrategy(operation, num1, num2);
    }
}

Postman測試

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xcONIEHF-1606586790792)(C:\Users\Liu-PC\AppData\Roaming\Typora\typora-user-images\image-20201129020539173.png)]

1-1=0

over ~ ~ ~

相關文章