飞道的博客

原来使用 Spring 实现策略模式可以这么简单!

408人阅读  评论(0)

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法,可以替代代码中大量的 if-else。

比如我们生活中的场景:买东西结账可以使用微信支付、支付宝支付或者银行卡支付,这些交易方式就是不同的策略。

那么在什么时候使用策略模式呢?

在《阿里巴巴Java开发手册》中有提到当超过 3 层的 if-else 的逻辑判断代码可以使用策略模式来实现。

在 Spring 中实现策略模式的方式有很多种,下面通过一个案例来演示下,比如有个需求需要实现支持第三方登录,目前需要支持以下三种登录方式:

  • 微信登录

  • QQ 登录

  • 微博登录

下面将通过策略模式来实现这个需求,其中策略模式结构如下图所示:

策略模式结构如下图所示:

策略模式结构

主要包括一个登录接口类和几种登录方式的实现方式,并利用简单工厂来获取对应的处理器。

定义策略接口

首先定义一个登录的策略接口 LoginHandler,其中包括两个方法:

  1. 获取策略类型的方法

  2. 处理策略逻辑的方法


   
  1. public  interface LoginHandler<T extends Serializable> {
  2.      /**
  3.      * 获取登录类型
  4.      *
  5.      * @return
  6.      */
  7.     LoginType getLoginType();
  8.      /**
  9.      * 登录
  10.      *
  11.      * @param request
  12.      * @return
  13.      */
  14.     LoginResponse<String, T> handleLogin(LoginRequest request);
  15. }

其中,LoginHandlergetLoginType 方法用来获取登录的类型(即策略类型),用于根据客户端传递的参数直接获取到对应的策略实现。

客户端传递的相关参数都被封装为 LoginRequest,传递给 handleLogin 进行处理。


   
  1. @Data
  2. public class LoginRequest {
  3.     private LoginType loginType;
  4.     private Long userId;
  5. }

其中,根据需求定义登录类型枚举如下:


   
  1. public enum LoginType {
  2.     QQ,
  3.     WE_CHAT,
  4.     WEI_BO;
  5. }

实现策略接口

在定义好策略接口后,我们就需要根据各种第三方登录来实现对应的处理逻辑就可以了。

微信登录


   
  1. @Component
  2. public class WeChatLoginHandler implements LoginHandler<String> {
  3.     private final Logger logger = LoggerFactory.getLogger(this.getClass());
  4.      /**
  5.      * 获取登录类型
  6.      *
  7.      * @return
  8.      */
  9.     @Override
  10.     public LoginType getLoginType() {
  11.          return LoginType.WE_CHAT;
  12.     }
  13.      /**
  14.      * 登录
  15.      *
  16.      * @param request
  17.      * @return
  18.      */
  19.     @Override
  20.     public LoginResponse<String, String> handleLogin(LoginRequest request) {
  21.         logger.info( "微信登录:userId:{}", request.getUserId());
  22.         String weChatName = getWeChatName(request);
  23.          return LoginResponse.success( "微信登录成功", weChatName);
  24.     }
  25.     private String getWeChatName(LoginRequest request) {
  26.          return  "wupx";
  27.     }
  28. }

QQ 登录


   
  1. @Component
  2. public class QQLoginHandler implements LoginHandler<Serializable> {
  3.     private final Logger logger = LoggerFactory.getLogger(this.getClass());
  4.      /**
  5.      * 获取登录类型
  6.      *
  7.      * @return
  8.      */
  9.     @Override
  10.     public LoginType getLoginType() {
  11.          return LoginType.QQ;
  12.     }
  13.      /**
  14.      * 登录
  15.      *
  16.      * @param request
  17.      * @return
  18.      */
  19.     @Override
  20.     public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {
  21.         logger.info( "QQ登录:userId:{}", request.getUserId());
  22.          return LoginResponse.success( "QQ登录成功", null);
  23.     }
  24. }

微博登录


   
  1. @Component
  2. public class WeiBoLoginHandler implements LoginHandler<Serializable> {
  3.     private final Logger logger = LoggerFactory.getLogger(this.getClass());
  4.      /**
  5.      * 获取登录类型
  6.      *
  7.      * @return
  8.      */
  9.     @Override
  10.     public LoginType getLoginType() {
  11.          return LoginType.WEI_BO;
  12.     }
  13.      /**
  14.      * 登录
  15.      *
  16.      * @param request
  17.      * @return
  18.      */
  19.     @Override
  20.     public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {
  21.         logger.info( "微博登录:userId:{}", request.getUserId());
  22.          return LoginResponse.success( "微博登录成功", null);
  23.     }
  24. }

创建策略的简单工厂


   
  1. @Component
  2. public class LoginHandlerFactory implements InitializingBean, ApplicationContextAware {
  3.     private static final Map<LoginType, LoginHandler<Serializable>> LOGIN_HANDLER_MAP =  new EnumMap<>(LoginType.class);
  4.     private ApplicationContext appContext;
  5.      /**
  6.      * 根据登录类型获取对应的处理器
  7.      *
  8.      * @param loginType 登录类型
  9.      * @return 登录类型对应的处理器
  10.      */
  11.     public LoginHandler<Serializable> getHandler(LoginType loginType) {
  12.          return LOGIN_HANDLER_MAP.get(loginType);
  13.     }
  14.     @Override
  15.     public void afterPropertiesSet() throws Exception {
  16.          // 将 Spring 容器中所有的 LoginHandler 注册到 LOGIN_HANDLER_MAP
  17.         appContext.getBeansOfType(LoginHandler.class)
  18.                 .values()
  19.                 .forEach(handler -> LOGIN_HANDLER_MAP.put(handler.getLoginType(), handler));
  20.     }
  21.     @Override
  22.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  23.         appContext = applicationContext;
  24.     }
  25. }

我们让 LoginHandlerFactory实现 InitializingBean 接口,在 afterPropertiesSet 方法中,基于 Spring 容器将所有 LoginHandler 自动注册到 LOGIN_HANDLER_MAP,从而 Spring 容器启动完成后, getHandler 方法可以直接通过 loginType 来获取对应的登录处理器。

创建登录服务

在登录服务中,我们通过 LoginHandlerFactory 来获取对应的登录处理器,从而处理不同类型的第三方登录:


   
  1. @Service
  2. public class LoginServiceImpl implements LoginService {
  3.     @Autowired
  4.     private LoginHandlerFactory loginHandlerFactory;
  5.     @Override
  6.     public LoginResponse<String, Serializable> login(LoginRequest request) {
  7.         LoginType loginType = request.getLoginType();
  8.          // 根据 loginType 找到对应的登录处理器
  9.         LoginHandler<Serializable> loginHandler =
  10.                 loginHandlerFactory.getHandler(loginType);
  11.          // 处理登录
  12.          return loginHandler.handleLogin(request);
  13.     }
  14. }

Factory 只负责获取 Handler,Handler 只负责处理具体的登录,Service 只负责逻辑编排,从而达到功能上的低耦合高内聚。

测试

写一个 Controller:


   
  1. @RestController
  2. public class LoginController {
  3.     @Autowired
  4.     private LoginService loginService;
  5.      /**
  6.      * 登录
  7.      */
  8.     @PostMapping( "/login")
  9.     public LoginResponse<String, Serializable> login(@RequestParam LoginType loginType, @RequestParam Long userId) {
  10.         LoginRequest loginRequest =  new LoginRequest();
  11.         loginRequest.setLoginType(loginType);
  12.         loginRequest.setUserId(userId);
  13.          return loginService.login(loginRequest);
  14.     }
  15. }

然后用 Postman 测下下:

微信登录
QQ登录

是不是很简单呢?如果需求又要加需求,需要支持 GitHub 第三方登录。

此时我们只需要添加一个新的策略实现,然后在登录枚举中加入对应的类型即可:


   
  1. @Component
  2. public class GitHubLoginHandler implements LoginHandler<Serializable> {
  3.     private final Logger logger = LoggerFactory.getLogger(this.getClass());
  4.      /**
  5.      * 获取登录类型
  6.      *
  7.      * @return
  8.      */
  9.     @Override
  10.     public LoginType getLoginType() {
  11.          return LoginType.GIT_HUB;
  12.     }
  13.      /**
  14.      * 登录
  15.      *
  16.      * @param request
  17.      * @return
  18.      */
  19.     @Override
  20.     public LoginResponse<String, Serializable> handleLogin(LoginRequest request) {
  21.         logger.info( "GitHub登录:userId:{}", request.getUserId());
  22.          return LoginResponse.success( "GitHub登录成功", null);
  23.     }
  24. }

此时不需要修改任何代码 ,因为 Spring 容器重启时会自动将 GitHubLoginHandler 注册到 LoginHandlerFactory 中,使用 Spring 实现策略模式就是这么简单,还不快学起来!


       

   

说完观察者和发布订阅模式的区别,面试官不留我吃饭了

模板方法模式——看看 JDK 和 Spring 是如何优雅复用代码的

从原型模式到浅拷贝和深拷贝

觉得不错,点个在看~


转载:https://blog.csdn.net/qq_27276045/article/details/113533404
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场