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

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

[[379778]]

本文转载自微信公众号「武培轩」,作者武培轩 。转载本文请联系武培轩公众号。

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

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

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

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

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

  • 微信登录
  • QQ 登录
  • 微博登录

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

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

策略模式结构

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

定义策略接口

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

获取策略类型的方法

处理策略逻辑的方法

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

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

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

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

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

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

实现策略接口

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

微信登录

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

QQ 登录

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

微博登录

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

创建策略的简单工厂

  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.      * 
  9.      * @param loginType 登录类型 
  10.      * @return 登录类型对应的处理器 
  11.      */ 
  12.     public LoginHandler<Serializable> getHandler(LoginType loginType) { 
  13.         return LOGIN_HANDLER_MAP.get(loginType); 
  14.     } 
  15.  
  16.     @Override 
  17.     public void afterPropertiesSet() throws Exception { 
  18.         // 将 Spring 容器中所有的 LoginHandler 注册到 LOGIN_HANDLER_MAP 
  19.         appContext.getBeansOfType(LoginHandler.class) 
  20.                 .values() 
  21.                 .forEach(handler -> LOGIN_HANDLER_MAP.put(handler.getLoginType(), handler)); 
  22.     } 
  23.  
  24.     @Override 
  25.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
  26.         appContext = applicationContext; 
  27.     } 

我们让 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.  
  6.     @Override 
  7.     public LoginResponse<String, Serializable> login(LoginRequest request) { 
  8.         LoginType loginType = request.getLoginType(); 
  9.         // 根据 loginType 找到对应的登录处理器 
  10.         LoginHandler<Serializable> loginHandler = 
  11.                 loginHandlerFactory.getHandler(loginType); 
  12.         // 处理登录 
  13.         return loginHandler.handleLogin(request); 
  14.     } 

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

测试

写一个 Controller:

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

然后用 Postman 测下下:

微信登录

 

QQ登录

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

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

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

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

 

责任编辑:武晓燕 来源: 武培轩
相关推荐

2020-09-25 07:49:36

策略模式Spring

2022-06-17 07:32:39

策略模式SpringBoot

2014-10-08 15:00:50

SUSE操作系统云计算

2010-08-02 13:55:20

2016-03-21 11:09:52

Tableau/大数据

2021-04-19 05:42:51

Mmap文件系统

2023-11-01 14:49:07

2020-11-02 14:38:56

Java 深度学习模型

2021-06-10 06:57:39

Redis存储数据库

2022-12-06 17:30:04

2020-09-24 06:44:54

HTTPS网站 HTTP

2020-11-27 10:34:01

HTTPHTTPS模型

2023-09-22 08:00:00

分布式锁Redis

2019-03-15 10:55:12

通信系统手机

2014-11-25 15:02:01

客服系统

2020-10-22 08:01:52

XMLJSON转换

2018-10-28 17:54:00

分布式事务数据

2022-01-27 14:12:49

Python游戏脚本

2019-05-27 14:03:48

开发技能代码

2021-12-30 10:55:54

Python游戏脚本
点赞
收藏

51CTO技术栈公众号