前言
最近在做一个项目的用户模块的认证功能,考虑到以后的扩展,于是想到了使用 设计模式
中的 策略模式
来实现。之前也看过不少使用 策略模式
的案例,但是感觉不是特别优雅,所以本文基于 枚举
配合 策略模式
来实现更灵活的功能扩展。
策略模式
策略模式定义
策略模式(Strategy Pattern)是一种行为设计模式,旨在定义一系列算法,将每一个算法封装起来,并使它们可以互换。策略模式允许算法的独立变化,而不需要修改使用这些算法的客户端代码。它的核心思想是将算法封装在独立的策略类中,并通过上下文类(Context)来使用这些策略。
策略模式组成
- 策略接口(Strategy): 定义一系列算法的共同接口。
- 具体策略(ConcreteStrategy): 实现策略接口的具体算法。
- 上下文(Context): 维护对某一策略对象的引用,并且可以使用这个策略来执行具体的算法。
策略模式结构
策略模式优缺点
优点
- 开闭原则: 策略模式使得系统对扩展开放,对修改封闭。你可以通过增加新的策略类来扩展系统的功能,而不需要修改现有的代码。
- 代码复用: 通过将算法封装到策略类中,可以避免代码的重复,并且可以在不同的上下文中重用这些策略。
- 减少条件语句: 在使用策略模式时,所有的条件语句都被移到了策略类中,从而使上下文类的代码更加清晰。
缺点
- 增加类的数量: 每个策略都需要一个新的类,这可能会增加系统中的类的数量,增加系统的复杂性。
- 客户端必须知道所有的策略: 客户端需要知道所有的策略,并选择适合的策略,这可能会增加客户端的复杂度。
传统策略模式
策略定义和实现
@Data
public class AuthDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private String cellphone;
}
public interface AuthStrategy {
/**
* 用户认证
*
* @param dto 认证参数
* @return {@link UsersVO }
*/
UsersVO auth(AuthDTO dto);
}
public class PasswordAuthStrategy implements AuthStrategy {
private static final Logger log = LoggerFactory.getLogger(PasswordAuthStrategy.class);
/**
* 用户名密码认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了用户名密码认证策略...");
return null;
}
}
public class SmsAuthStrategy implements AuthStrategy {
private static final Logger log = LoggerFactory.getLogger(SmsAuthStrategy.class);
/**
* 短信认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了短信认证策略...");
return null;
}
}
public class WechatCodeAuthStrategy implements AuthStrategy {
private static final Logger log = LoggerFactory.getLogger(WechatCodeAuthStrategy.class);
/**
* 微信扫码认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了微信扫码认证策略...");
return null;
}
}
策略上下文
public class AuthStrategyContext {
private AuthStrategy authStrategy;
public void setAuthStrategy(AuthStrategy authStrategy) {
this.authStrategy = authStrategy;
}
public UsersVO auth(AuthDTO dto) {
if (null == authStrategy) {
throw new BusinessException("认证策略不能为空");
}
return authStrategy.auth(dto);
}
}
传统策略模式实现
@Component
public class UserServiceImpl implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
/**
* 自定义Spring Security认证逻辑
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AuthDTO authDTO;
// 解析传入的json参数为AuthParamsDTO
try {
authDTO = JSON.parseObject(username, AuthDTO.class);
} catch (Exception e) {
e.printStackTrace();
logger.error("请求认证参数格式不正确, 认证参数: {}, 异常信息: []", username, e);
throw new RuntimeException("请求认证参数格式不正确!");
}
AuthStrategyContext authStrategyContext = new AuthStrategyContext();
authStrategyContext.setAuthStrategy(new PasswordAuthStrategy());
// authStrategyContext.setAuthStrategy(new SmsAuthStrategy());
// authStrategyContext.setAuthStrategy(new WechatCodeAuthStrategy());
UsersVO usersVO = authStrategyContext.auth(authDTO);
// 构建用户身份信息
return buildUserPrincipal(usersVO);
}
}
策略模式加枚举
策略定义和实现
@Data
public class AuthDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private String cellphone;
/**
* 认证的类型 password:用户名密码认证 sms:短信认证 wechat_code:微信扫码认证
*/
private String authType;
}
public enum AuthTypeEnum {
/**
* 用户名密码认证
*/
PASSWORD("password", "用户名密码认证"),
/**
* 短信认证
*/
SMS("sms", "短信认证"),
/**
* 微信扫码认证
*/
WECHAT_CODE("wechat_code", "微信扫码认证");
/**
* 认证类型
*/
private final String type;
/**
* 认证类型值
*/
private final String text;
public String getType() {
return type;
}
public String getText() {
return text;
}
AuthTypeEnum(String type, String text) {
this.type = type;
this.text = text;
}
}
public interface AuthStrategy {
/**
* 认证类型枚举
*/
AuthTypeEnum getAuthTypeEnum();
/**
* 认证
*
* @param dto 认证参数
* @return {@link UsersVO }
*/
UsersVO auth(AuthDTO dto);
}
@Slf4j
@Component(value = "passwordAuthStrategy")
public class PasswordAuthStrategy implements AuthStrategy {
/**
* 用户名密码类型
*/
@Override
public AuthTypeEnum getAuthTypeEnum() {
return AuthTypeEnum.PASSWORD;
}
/**
* 用户名密码认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了用户名密码认证策略...");
return null;
}
}
@Slf4j
@Component(value = "smsAuthStrategy")
public class SmsAuthStrategy implements AuthStrategy {
/**
* 短信类型
*/
@Override
public AuthTypeEnum getAuthTypeEnum() {
return AuthTypeEnum.SMS;
}
/**
* 短信认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了短信认证策略...");
return null;
}
}
@Slf4j
@Component(value = "wechatCodeAuthStrategy")
public class WechatCodeAuthStrategy implements AuthStrategy {
/**
* 微信扫码认证类型
*/
@Override
public AuthTypeEnum getAuthTypeEnum() {
return AuthTypeEnum.WECHAT_CODE;
}
/**
* 微信扫码认证
*/
@Override
public UsersVO auth(AuthDTO dto) {
log.info("执行了微信扫码认证策略...");
return null;
}
}
策略上下文
@Component
public class AuthStrategyContext {
@Resource
private ApplicationContext applicationContext;
/**
* 存储策略类型和对应的策略实例
*/
public final Map<String, AuthStrategy> strategyMap = new ConcurrentHashMap<>(16);
/**
* 初始化方法,在Bean创建后调用
* 该方法获取所有AuthStrategy实例,并将它们存储在strategyMap中
* 以便根据类型快速查找对应的策略。
*/
@PostConstruct
public void init() {
// 获取实现AuthStrategy接口的所有实例bean
Map<String, AuthStrategy> map = applicationContext.getBeansOfType(AuthStrategy.class);
map.forEach((k, v) -> strategyMap.put(v.getAuthTypeEnum().getType(), v));
}
/**
* 根据枚举类型获取对应的策略
*/
public AuthStrategy getAuthStrategy(String enumType) {
return strategyMap.get(enumType);
}
}
策略模式配合枚举实现
@Component
public class UserServiceImpl implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Resource
private AuthStrategyContext authStrategyContext;
/**
* 自定义Spring Security认证逻辑
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AuthDTO authDTO;
// 解析传入的json参数为AuthParamsDTO
try {
authDTO = JSON.parseObject(username, AuthDTO.class);
} catch (Exception e) {
e.printStackTrace();
logger.error("请求认证参数格式不正确, 认证参数: {}, 异常信息: []", username, e);
throw new RuntimeException("请求认证参数格式不正确!");
}
// 获取认证类型,根据认证类型获取对应的策略
String authType = authDTO.getAuthType();
AuthStrategy authStrategy = authStrategyContext.getAuthStrategy(authType);
UsersVO usersVO = authStrategy.auth(authDTO);
// 构建用户身份信息
return buildUserPrincipal(usersVO);
}
}
总结
可以看到,引入策略枚举后,在认证策略上下文里面,Spring
启动时就将所有的认证策略注入到容器中,后续直接通过认证类型就能获取到对应的认证策略,相比传统的策略模式更加清晰明了。使用策略模式虽然增加了大量的代码,但是对于后期的可扩展性和可维护性却有着很大的提升。好了就说这么多吧,要继续搬砖了。