設計模式最佳套路—— 愉快地使用策略模式

策略模式(Strategy Pattern) 定義了一組策略,分別在不同類中封裝起來,每種策略都可以根據當前場景相互替換,從而使策略的變化可以獨立於操作者。比如我們要去某個地方,會根據距離的不同(或者是根據手頭經濟狀況)來選擇不同的出行方式(共享單車、坐公交、滴滴打車等等),這些出行方式即不同的策略。

何時使用策略模式


阿里開發規約 - 編程規約 - 控制語句 - 第六條 :超過 3 層的 if-else 的邏輯判斷代碼可以使用衛語句、策略模式、狀態模式等來實現。相信大家都見過這種代碼:

if (conditionA) {
    邏輯1
} else if (conditionB) {
    邏輯2
} else if (conditionC) {
    邏輯3
} else {
    邏輯4
}

這種代碼雖然寫起來簡單,但是很明顯違反了面向對象的 2 個基本原則:

因爲違反了以上兩個原則,尤其是當 if-else 塊中的代碼量比較大時,後續代碼的擴展和維護就會逐漸變得非常困難且容易出錯,使用衛語句也同樣避免不了以上兩個問題。因此根據我的經驗,得出一個我個人認爲比較好的實踐:

愉快地使用策略模式

在 Spring 中,實現策略模式的方法多種多樣,下面我分享一下我目前實現策略模式的 “最佳套路”(如果你有更好的套路,歡迎賜教,一起討論哦)。

** 需求背景**

我們平臺的動態表單,之前專門用於模型輸入的提交。現在業務方希望對錶單能力進行開放,除了可用於模型提交,還可以用於業務方指定功能的提交(方式設計爲綁定一個 HSF 泛化服務,HSF 即淘系內部的 RPC 框架)。加上我們在配置表單時的 “預覽模式” 下的提交,那麼表單目前便有以下三種提交類型:

現在,有請我的 “最佳套路” 上場。

第一步,定義策略接口

首先定義策略的接口,包括兩個方法:

1、獲取策略類型的方法

2、處理策略邏輯的方法

/**
 * 表單提交處理器
 */
public interface FormSubmitHandler<R extends Serializable> {
    /**
     * 獲得提交類型(返回值也可以使用已經存在的枚舉類)
     *
     * @return 提交類型
     */
    String getSubmitType();
    /**
     * 處理表單提交請求
     *
     * @param request 請求
     * @return 響應,left 爲返回給前端的提示信息,right 爲業務值
     */
    CommonPairResponse<String, R> handleSubmit(FormSubmitRequest request);
}
/**
 * 表單提交的請求
 */
@Getter
@Setter
public class FormSubmitRequest {
    /**
     * 提交類型
     *
     * @see FormSubmitHandler#getSubmitType()
     */
    private String submitType;
    /**
     * 用戶 id
     */
    private Long userId;
    /**
     * 表單提交的值
     */
    private Map<String, Object> formInput;
    // 其他屬性
}

其中,FormSubmitHandler 的 getSubmitType 方法用來獲取表單的提交類型(即策略類型),用於根據客戶端傳遞的參數直接獲取到對應的策略實現;客戶端傳遞的相關參數都被封裝爲 FormSubmitRequest,傳遞給 handleSubmit 進行處理。

第二步,相關策略實現

預覽表單時的提交

@Component
public class FormPreviewSubmitHandler implements FormSubmitHandler<Serializable> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public String getSubmitType() { return "preview"; }
    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("預覽模式提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());
        return CommonPairResponse.success("預覽模式提交數據成功!", null);
    }
}

模型輸入時的提交

@Component
public class FormModelSubmitHandler implements FormSubmitHandler<Long> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public String getSubmitType() { return "model"; }
    @Override
    public CommonPairResponse<String, Long> handleSubmit(FormSubmitRequest request) {
        logger.info("模型提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());
        // 模型創建成功後獲得模型的 id
        Long modelId = createModel(request);
        return CommonPairResponse.success("模型提交成功!", modelId);
    }
    private Long createModel(FormSubmitRequest request) {
        // 創建模型的邏輯
        return 123L;
    }
}

HSF 模式的提交

@Component
public class FormHsfSubmitHandler implements FormSubmitHandler<Serializable> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public String getSubmitType() { return "hsf"; }
    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("HSF 模式提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());
        // 進行 HSF 泛化調用,獲得業務方返回的提示信息和業務數據
        CommonPairResponse<String, Serializable> response = hsfSubmitData(request);
        return response;
    }
    ...
}

第三步,建立策略的簡單工廠

@Component
public class FormSubmitHandlerFactory implements InitializingBean, ApplicationContextAware {
    private static final
    Map<String, FormSubmitHandler<Serializable>> FORM_SUBMIT_HANDLER_MAP = new HashMap<>(8);
    private ApplicationContext appContext;
    /**
     * 根據提交類型獲取對應的處理器
     *
     * @param submitType 提交類型
     * @return 提交類型對應的處理器
     */
    public FormSubmitHandler<Serializable> getHandler(String submitType) {
        return FORM_SUBMIT_HANDLER_MAP.get(submitType);
    }
    @Override
    public void afterPropertiesSet() {
        // 將 Spring 容器中所有的 FormSubmitHandler 註冊到 FORM_SUBMIT_HANDLER_MAP
        appContext.getBeansOfType(FormSubmitHandler.class)
                  .values()
                  .forEach(handler -> FORM_SUBMIT_HANDLER_MAP.put(handler.getSubmitType(), handler));
    }
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
        appContext = applicationContext;
    }
}

我們讓 FormSubmitHandlerFactory 實現 InitializingBean 接口,在 afterPropertiesSet 方法中,基於 Spring 容器將所有 FormSubmitHandler 自動註冊到 FORM_SUBMIT_HANDLER_MAP,從而 Spring 容器啓動完成後, getHandler 方法可以直接通過 submitType 來獲取對應的表單提交處理器。

第四步,使用 & 測試

在表單服務中,我們通過 FormSubmitHandlerFactory 來獲取對應的表單提交處理器,從而處理不同類型的提交:

@Service
public class FormServiceImpl implements FormService {
    @Autowired
    private FormSubmitHandlerFactory submitHandlerFactory;
    public CommonPairResponse<String, Serializable> submitForm(@NonNull FormSubmitRequest request) {
        String submitType = request.getSubmitType();
        // 根據 submitType 找到對應的提交處理器
        FormSubmitHandler<Serializable> submitHandler = submitHandlerFactory.getHandler(submitType);
        // 判斷 submitType 對應的 handler 是否存在
        if (submitHandler == null) {
            return CommonPairResponse.failure("非法的提交類型: " + submitType);
        }
        // 處理提交
        return submitHandler.handleSubmit(request);
    }
}

Factory 只負責獲取 Handler,Handler 只負責處理具體的提交,Service 只負責邏輯編排,從而達到功能上的 “低耦合高內聚”。

寫一個簡單的 Controller:

@RestController
public class SimpleController {
    @Autowired
    private FormService formService;
    @PostMapping("/form/submit")
    public CommonPairResponse<String, Serializable> submitForm(@RequestParam String submitType,
                                                               @RequestParam String formInputJson) {
        JSONObject formInput = JSON.parseObject(formInputJson);
        FormSubmitRequest request = new FormSubmitRequest();
        request.setUserId(123456L);
        request.setSubmitType(submitType);
        request.setFormInput(formInput);
        return formService.submitForm(request);
    }
}

最後來個簡單的測試:

我感覺到了,這就是非常流暢的感覺~

設想一次擴展

如果我們需要加入一個新的策略,比如綁定 FaaS 函數的提交,我們只需要添加一個新的策略實現即可:

@Component
public class FormFaasSubmitHandler implements FormSubmitHandler<Serializable> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public String getSubmitType() { return "faas"; }
    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("FaaS 模式的提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());
        // 進行 FaaS 函數調用,並獲得業務方返回的提示信息和業務數據
        CommonPairResponse<String, Serializable> response = faasSubmitData(request);
        return response;
    }
    ... 
}

此時不需要修改任何代碼,因爲 Spring 容器重啓時會自動將 FormFaasSubmitHandler 註冊到 FormSubmitHandlerFactory 中 —— 面向 Spring 編程,太香惹~

作者 | 周密(之葉)

編輯 | 橙子君

出品 | 阿里巴巴新零售淘系技術

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&amp;mid=2650412376&amp;idx=1&amp;sn=da835d9366fef6a903c8d17a4fb2e53e&amp;chksm=8396dd40b4e154563f856a86ce6a3b240ed832ef509805d29da18e12f5b0cda786e3b76e21ff&amp;scene=21#wechat_redirect