原來使用 Spring 實現(xiàn)策略模式可以這么簡單!
本文轉(zhuǎn)載自微信公眾號「武培軒」,作者武培軒 。轉(zhuǎn)載本文請聯(lián)系武培軒公眾號。
策略模式作為一種軟件設(shè)計模式,指對象有某個行為,但是在不同的場景中,該行為有不同的實現(xiàn)算法,可以替代代碼中大量的 if-else。
比如我們生活中的場景:買東西結(jié)賬可以使用微信支付、支付寶支付或者銀行卡支付,這些交易方式就是不同的策略。
那么在什么時候使用策略模式呢?
在《阿里巴巴Java開發(fā)手冊》中有提到當超過 3 層的 if-else 的邏輯判斷代碼可以使用策略模式來實現(xiàn)。
在 Spring 中實現(xiàn)策略模式的方式有很多種,下面通過一個案例來演示下,比如有個需求需要實現(xiàn)支持第三方登錄,目前需要支持以下三種登錄方式:
- 微信登錄
- QQ 登錄
- 微博登錄
下面將通過策略模式來實現(xiàn)這個需求,其中策略模式結(jié)構(gòu)如下圖所示:
策略模式結(jié)構(gòu)如下圖所示:
策略模式結(jié)構(gòu)
主要包括一個登錄接口類和幾種登錄方式的實現(xiàn)方式,并利用簡單工廠來獲取對應(yīng)的處理器。
定義策略接口
首先定義一個登錄的策略接口 LoginHandler,其中包括兩個方法:
獲取策略類型的方法
處理策略邏輯的方法
- public interface LoginHandler<T extends Serializable> {
- /**
- * 獲取登錄類型
- *
- * @return
- */
- LoginType getLoginType();
- /**
- * 登錄
- *
- * @param request
- * @return
- */
- LoginResponse<String, T> handleLogin(LoginRequest request);
- }
其中,LoginHandler 的 getLoginType 方法用來獲取登錄的類型(即策略類型),用于根據(jù)客戶端傳遞的參數(shù)直接獲取到對應(yīng)的策略實現(xiàn)。
客戶端傳遞的相關(guān)參數(shù)都被封裝為 LoginRequest,傳遞給 handleLogin 進行處理。
- @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<String, String> 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<String, Serializable> 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<String, Serializable> 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<LoginType, LoginHandler<Serializable>> LOGIN_HANDLER_MAP = new EnumMap<>(LoginType.class);
- private ApplicationContext appContext;
- /**
- * 根據(jù)登錄類型獲取對應(yīng)的處理器
- *
- * @param loginType 登錄類型
- * @return 登錄類型對應(yīng)的處理器
- */
- public LoginHandler<Serializable> 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<String, Serializable> login(LoginRequest request) {
- LoginType loginType = request.getLoginType();
- // 根據(jù) loginType 找到對應(yīng)的登錄處理器
- LoginHandler<Serializable> loginHandler =
- loginHandlerFactory.getHandler(loginType);
- // 處理登錄
- return loginHandler.handleLogin(request);
- }
- }
Factory 只負責獲取 Handler,Handler 只負責處理具體的登錄,Service 只負責邏輯編排,從而達到功能上的低耦合高內(nèi)聚。
測試
寫一個 Controller:
- @RestController
- public class LoginController {
- @Autowired
- private LoginService loginService;
- /**
- * 登錄
- */
- @PostMapping("/login")
- public LoginResponse<String, Serializable> login(@RequestParam LoginType loginType, @RequestParam Long userId) {
- LoginRequest loginRequest = new LoginRequest();
- loginRequest.setLoginType(loginType);
- loginRequest.setUserId(userId);
- return loginService.login(loginRequest);
- }
- }
然后用 Postman 測下下:
微信登錄
QQ登錄
是不是很簡單呢?如果需求又要加需求,需要支持 GitHub 第三方登錄。
此時我們只需要添加一個新的策略實現(xiàn),然后在登錄枚舉中加入對應(yīng)的類型即可:
- @Component
- public class GitHubLoginHandler implements LoginHandler<Serializable> {
- private final Logger logger = LoggerFactory.getLogger(this.getClass());
- /**
- * 獲取登錄類型
- *
- * @return
- */
- @Override
- public LoginType getLoginType() {
- return LoginType.GIT_HUB;
- }
- /**
- * 登錄
- *
- * @param request
- * @return
- */
- @Override
- public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {
- logger.info("GitHub登錄:userId:{}", request.getUserId());
- return LoginResponse.success("GitHub登錄成功", null);
- }
- }
此時不需要修改任何代碼 ,因為 Spring 容器重啟時會自動將 GitHubLoginHandler 注冊到 LoginHandlerFactory 中,使用 Spring 實現(xiàn)策略模式就是這么簡單,還不快學起來!