設計模式最佳套路—— 愉快地使用策略模式
策略模式(Strategy Pattern) 定義了一組策略,分別在不同類中封裝起來,每種策略都可以根據當前場景相互替換,從而使策略的變化可以獨立於操作者。比如我們要去某個地方,會根據距離的不同(或者是根據手頭經濟狀況)來選擇不同的出行方式(共享單車、坐公交、滴滴打車等等),這些出行方式即不同的策略。
何時使用策略模式
阿里開發規約 - 編程規約 - 控制語句 - 第六條 :超過 3 層的 if-else 的邏輯判斷代碼可以使用衛語句、策略模式、狀態模式等來實現。相信大家都見過這種代碼:
if (conditionA) {
邏輯1
} else if (conditionB) {
邏輯2
} else if (conditionC) {
邏輯3
} else {
邏輯4
}
這種代碼雖然寫起來簡單,但是很明顯違反了面向對象的 2 個基本原則:
-
單一職責原則(一個類應該只有一個發生變化的原因):因爲之後修改任何一個邏輯,當前類都會被修改
-
開閉原則(對擴展開放,對修改關閉):如果此時需要添加(刪除)某個邏輯,那麼不可避免的要修改原來的代碼
因爲違反了以上兩個原則,尤其是當 if-else 塊中的代碼量比較大時,後續代碼的擴展和維護就會逐漸變得非常困難且容易出錯,使用衛語句也同樣避免不了以上兩個問題。因此根據我的經驗,得出一個我個人認爲比較好的實踐:
-
if-else 不超過 2 層,塊中代碼 1~5 行,直接寫到塊中,否則封裝爲方法
-
if-else 超過 2 層,但塊中的代碼不超過 3 行,儘量使用衛語句
-
if-else 超過 2 層,且塊中代碼超過 3 行,儘量使用策略模式
愉快地使用策略模式
在 Spring 中,實現策略模式的方法多種多樣,下面我分享一下我目前實現策略模式的 “最佳套路”(如果你有更好的套路,歡迎賜教,一起討論哦)。
** 需求背景**
我們平臺的動態表單,之前專門用於模型輸入的提交。現在業務方希望對錶單能力進行開放,除了可用於模型提交,還可以用於業務方指定功能的提交(方式設計爲綁定一個 HSF 泛化服務,HSF 即淘系內部的 RPC 框架)。加上我們在配置表單時的 “預覽模式” 下的提交,那麼表單目前便有以下三種提交類型:
-
預覽表單時的提交
-
模型輸入時的提交
-
綁定 HSF 時的提交
現在,有請我的 “最佳套路” 上場。
第一步,定義策略接口
首先定義策略的接口,包括兩個方法:
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==&mid=2650412376&idx=1&sn=da835d9366fef6a903c8d17a4fb2e53e&chksm=8396dd40b4e154563f856a86ce6a3b240ed832ef509805d29da18e12f5b0cda786e3b76e21ff&scene=21#wechat_redirect