原來使用 Spring 實現(xiàn)策略模式可以這么簡單!
策略模式作為一種軟件設(shè)計模式,指對象有某個行為,但是在不同的場景中,該行為有不同的實現(xiàn)算法,可以替代代碼中大量的 if-else。
比如我們生活中的場景:買東西結(jié)賬可以使用微信支付、支付寶支付或者銀行卡支付,這些交易方式就是不同的策略。
那么在什么時候使用策略模式呢?
在《阿里巴巴Java開發(fā)手冊》中有提到當(dāng)超過 3 層的 if-else 的邏輯判斷代碼可以使用策略模式來實現(xiàn)。

在 Spring 中實現(xiàn)策略模式的方式有很多種,下面通過一個案例來演示下,比如有個需求需要實現(xiàn)支持第三方登錄,目前需要支持以下三種登錄方式:
微信登錄 QQ 登錄 微博登錄
下面將通過策略模式來實現(xiàn)這個需求,其中策略模式結(jié)構(gòu)如下圖所示:
策略模式結(jié)構(gòu)如下圖所示:

主要包括一個登錄接口類和幾種登錄方式的實現(xiàn)方式,并利用簡單工廠來獲取對應(yīng)的處理器。
定義策略接口
首先定義一個登錄的策略接口 LoginHandler,其中包括兩個方法:
獲取策略類型的方法 處理策略邏輯的方法
public?interface?LoginHandler<T?extends?Serializable>?{
????/**
?????*?獲取登錄類型
?????*
?????*?@return
?????*/
????LoginType?getLoginType();
????/**
?????*?登錄
?????*
?????*?@param?request
?????*?@return
?????*/
????LoginResponse?handleLogin(LoginRequest?request) ;
}
其中,LoginHandler 的 getLoginType 方法用來獲取登錄的類型(即策略類型),用于根據(jù)客戶端傳遞的參數(shù)直接獲取到對應(yīng)的策略實現(xiàn)。
客戶端傳遞的相關(guān)參數(shù)都被封裝為 LoginRequest,傳遞給 handleLogin 進(jìn)行處理。
@Data
public?class?LoginRequest?{
????private?LoginType?loginType;
????private?Long?userId;
}
其中,根據(jù)需求定義登錄類型枚舉如下:
public?enum?LoginType?{
????QQ,
????WE_CHAT,
????WEI_BO;
}
實現(xiàn)策略接口
在定義好策略接口后,我們就需要根據(jù)各種第三方登錄來實現(xiàn)對應(yīng)的處理邏輯就可以了。
微信登錄
@Component
public?class?WeChatLoginHandler?implements?LoginHandler<String>?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?獲取登錄類型
?????*
?????*?@return
?????*/
????@Override
????public?LoginType?getLoginType()?{
????????return?LoginType.WE_CHAT;
????}
????/**
?????*?登錄
?????*
?????*?@param?request
?????*?@return
?????*/
????@Override
????public?LoginResponse?handleLogin(LoginRequest?request)? {
????????logger.info("微信登錄:userId:{}",?request.getUserId());
????????String?weChatName?=?getWeChatName(request);
????????return?LoginResponse.success("微信登錄成功",?weChatName);
????}
????private?String?getWeChatName(LoginRequest?request)?{
????????return?"wupx";
????}
}
QQ 登錄
@Component
public?class?QQLoginHandler?implements?LoginHandler<Serializable>?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?獲取登錄類型
?????*
?????*?@return
?????*/
????@Override
????public?LoginType?getLoginType()?{
????????return?LoginType.QQ;
????}
????/**
?????*?登錄
?????*
?????*?@param?request
?????*?@return
?????*/
????@Override
????public?LoginResponse?handleLogin(LoginRequest?request)? {
????????logger.info("QQ登錄:userId:{}",?request.getUserId());
????????return?LoginResponse.success("QQ登錄成功",?null);
????}
}
微博登錄
@Component
public?class?WeiBoLoginHandler?implements?LoginHandler<Serializable>?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?獲取登錄類型
?????*
?????*?@return
?????*/
????@Override
????public?LoginType?getLoginType()?{
????????return?LoginType.WEI_BO;
????}
????/**
?????*?登錄
?????*
?????*?@param?request
?????*?@return
?????*/
????@Override
????public?LoginResponse?handleLogin(LoginRequest?request)? {
????????logger.info("微博登錄:userId:{}",?request.getUserId());
????????return?LoginResponse.success("微博登錄成功",?null);
????}
}
創(chuàng)建策略的簡單工廠
@Component
public?class?LoginHandlerFactory?implements?InitializingBean,?ApplicationContextAware?{
????private?static?final?Map>?LOGIN_HANDLER_MAP?=?new?EnumMap<>(LoginType.class);
????private?ApplicationContext?appContext;
????/**
?????*?根據(jù)登錄類型獲取對應(yīng)的處理器
?????*
?????*?@param?loginType?登錄類型
?????*?@return?登錄類型對應(yīng)的處理器
?????*/
????public?LoginHandler?getHandler(LoginType?loginType)? {
????????return?LOGIN_HANDLER_MAP.get(loginType);
????}
????@Override
????public?void?afterPropertiesSet()?throws?Exception?{
????????//?將?Spring?容器中所有的?LoginHandler?注冊到?LOGIN_HANDLER_MAP
????????appContext.getBeansOfType(LoginHandler.class)
????????????????.values()
????????????????.forEach(handler?->?LOGIN_HANDLER_MAP.put(handler.getLoginType(),?handler));
????}
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????appContext?=?applicationContext;
????}
}
我們讓 LoginHandlerFactory實現(xiàn) InitializingBean 接口,在 afterPropertiesSet 方法中,基于 Spring 容器將所有 LoginHandler 自動注冊到 LOGIN_HANDLER_MAP,從而 Spring 容器啟動完成后, getHandler 方法可以直接通過 loginType 來獲取對應(yīng)的登錄處理器。
創(chuàng)建登錄服務(wù)
在登錄服務(wù)中,我們通過 LoginHandlerFactory 來獲取對應(yīng)的登錄處理器,從而處理不同類型的第三方登錄:
@Service
public?class?LoginServiceImpl?implements?LoginService?{
????@Autowired
????private?LoginHandlerFactory?loginHandlerFactory;
????@Override
????public?LoginResponse?login(LoginRequest?request)? {
????????LoginType?loginType?=?request.getLoginType();
????????//?根據(jù)?loginType?找到對應(yīng)的登錄處理器
????????LoginHandler?loginHandler?=
????????????????loginHandlerFactory.getHandler(loginType);
????????//?處理登錄
????????return?loginHandler.handleLogin(request);
????}
}
Factory 只負(fù)責(zé)獲取 Handler,Handler 只負(fù)責(zé)處理具體的登錄,Service 只負(fù)責(zé)邏輯編排,從而達(dá)到功能上的低耦合高內(nèi)聚。
測試
寫一個 Controller:
@RestController
public?class?LoginController?{
????@Autowired
????private?LoginService?loginService;
????/**
?????*?登錄
?????*/
????@PostMapping("/login")
????public?LoginResponse?login(@RequestParam?LoginType?loginType,?@RequestParam?Long?userId)? {
????????LoginRequest?loginRequest?=?new?LoginRequest();
????????loginRequest.setLoginType(loginType);
????????loginRequest.setUserId(userId);
????????return?loginService.login(loginRequest);
????}
}
然后用 Postman 測下下:


是不是很簡單呢?如果需求又要加需求,需要支持 GitHub 第三方登錄。
此時我們只需要添加一個新的策略實現(xiàn),然后在登錄枚舉中加入對應(yīng)的類型即可:
@Component
public?class?GitHubLoginHandler?implements?LoginHandler?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?獲取登錄類型
?????*
?????*?@return
?????*/
????@Override
????public?LoginType?getLoginType()?{
????????return?LoginType.GIT_HUB;
????}
????/**
?????*?登錄
?????*
?????*?@param?request
?????*?@return
?????*/
????@Override
????public?LoginResponse?handleLogin(LoginRequest?request)?{
????????logger.info("GitHub登錄:userId:{}",?request.getUserId());
????????return?LoginResponse.success("GitHub登錄成功",?null);
????}
}
此時不需要修改任何代碼 ,因為 Spring 容器重啟時會自動將 GitHubLoginHandler 注冊到 LoginHandlerFactory 中,使用 Spring 實現(xiàn)策略模式就是這么簡單,還不快學(xué)起來!
完
? ? ? ?
???覺得不錯,點個在看~

