<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          原來使用 Spring 實(shí)現(xiàn)策略模式可以這么簡單!

          共 11339字,需瀏覽 23分鐘

           ·

          2021-04-23 11:30








          策略模式作為一種軟件設(shè)計(jì)模式,指對(duì)象有某個(gè)行為,但是在不同的場景中,該行為有不同的實(shí)現(xiàn)算法,可以替代代碼中大量的 if-else。
          比如我們生活中的場景:買東西結(jié)賬可以使用微信支付、支付寶支付或者銀行卡支付,這些交易方式就是不同的策略。
          那么在什么時(shí)候使用策略模式呢?
          在《阿里巴巴Java開發(fā)手冊》中有提到當(dāng)超過 3 層的 if-else 的邏輯判斷代碼可以使用策略模式來實(shí)現(xiàn)。
          在 Spring 中實(shí)現(xiàn)策略模式的方式有很多種,下面通過一個(gè)案例來演示下,比如有個(gè)需求需要實(shí)現(xiàn)支持第三方登錄,目前需要支持以下三種登錄方式:
          • 微信登錄
          • QQ 登錄
          • 微博登錄
          下面將通過策略模式來實(shí)現(xiàn)這個(gè)需求,其中策略模式結(jié)構(gòu)如下圖所示:
          策略模式結(jié)構(gòu)如下圖所示:
          策略模式結(jié)構(gòu)
          主要包括一個(gè)登錄接口類和幾種登錄方式的實(shí)現(xiàn)方式,并利用簡單工廠來獲取對(duì)應(yīng)的處理器。

           

          定義策略接口

          首先定義一個(gè)登錄的策略接口 LoginHandler,其中包括兩個(gè)方法:
          1. 獲取策略類型的方法
          2. 處理策略邏輯的方法

          public interface LoginHandler<T extends Serializable{

              /**
               * 獲取登錄類型
               *
               * @return
               */

              LoginType getLoginType();

              /**
               * 登錄
               *
               * @param request
               * @return
               */

              LoginResponse<String, T> handleLogin(LoginRequest request);
          }

          其中,LoginHandlergetLoginType 方法用來獲取登錄的類型(即策略類型),用于根據(jù)客戶端傳遞的參數(shù)直接獲取到對(duì)應(yīng)的策略實(shí)現(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;
          }

           

           

          實(shí)現(xiàn)策略接口

          在定義好策略接口后,我們就需要根據(jù)各種第三方登錄來實(shí)現(xiàn)對(duì)應(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 InitializingBeanApplicationContextAware {
              private static final Map<LoginType, LoginHandler<Serializable>> LOGIN_HANDLER_MAP = new EnumMap<>(LoginType.class);
              private ApplicationContext appContext;

              /**
               * 根據(jù)登錄類型獲取對(duì)應(yīng)的處理器
               *
               * @param loginType 登錄類型
               * @return 登錄類型對(duì)應(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實(shí)現(xiàn) InitializingBean 接口,在 afterPropertiesSet 方法中,基于 Spring 容器將所有 LoginHandler 自動(dòng)注冊到 LOGIN_HANDLER_MAP,從而 Spring 容器啟動(dòng)完成后, getHandler 方法可以直接通過 loginType 來獲取對(duì)應(yīng)的登錄處理器。

           

          創(chuàng)建登錄服務(wù)

          在登錄服務(wù)中,我們通過 LoginHandlerFactory 來獲取對(duì)應(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 找到對(duì)應(yīng)的登錄處理器
                  LoginHandler<Serializable> loginHandler =
                          loginHandlerFactory.getHandler(loginType);
                  // 處理登錄
                  return loginHandler.handleLogin(request);
              }
          }

          Factory 只負(fù)責(zé)獲取 Handler,Handler 只負(fù)責(zé)處理具體的登錄,Service 只負(fù)責(zé)邏輯編排,從而達(dá)到功能上的低耦合高內(nèi)聚。

           

          測試

          寫一個(gè) 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 第三方登錄。
          此時(shí)我們只需要添加一個(gè)新的策略實(shí)現(xiàn),然后在登錄枚舉中加入對(duì)應(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);
              }
          }

          此時(shí)不需要修改任何代碼 ,因?yàn)?Spring 容器重啟時(shí)會(huì)自動(dòng)將 GitHubLoginHandler 注冊到 LoginHandlerFactory 中,使用 Spring 實(shí)現(xiàn)策略模式就是這么簡單,還不快學(xué)起來!

          有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號(hào)


          好文章,我在看??

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月色丁香国产精品 | 日韩欧美卡一卡二 | 黄片三区四区 | 成年人A片 | 亚州老女人BB |