策略模式
策略模式是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。这样,算法的变化就可以独立于使用它的客户端。
策略模式的实现
场景:为了满足不同用户的登录需求,系统需要支持多种登录方式,包括用户名密码、手机号验证码及邮箱验证码登录。
在上面,我们可以看到以下元素体现了策略模式:
- 接口 (LoginStrategy): 定义了所有支持的各种算法(即认证策略)的公共接口。在这里,login() 方法是所有策略都需要实现的行为。
- 具体策略类 (PasswordStrategy, EmailStrategy, PhoneAuthStrategy): 这些类实现了 AuthStrategy 接口,并提供了具体的实现。每一种认证方式都有自己的实现。
- 上下文 (AuthStrategyFactory): 负责管理策略对象,并将策略对象提供给客户端。在这个例子中,AuthStrategyFactory 包含了一个 Map<String, AuthStrategy>,用于存储不同类型的认证策略,并提供方法 getAuthStrategy() 来根据某种认证类型获取对应的策略实例。
具体的策略工厂代码实现
public class AuthStrategySingleFactory {
private static final EnumMap<AuthEnumType,AuthStrategy> authStrategyMap =
new EnumMap<>(AuthEnumType.class);
static {
authStrategyMap.put(AuthEnumType.PHONE,new SmsAuthStrategy());
authStrategyMap.put(AuthEnumType.PASSWORD,new PasswordAuthStrategy());
authStrategyMap.put(AuthEnumType.EMAIL, new EmailAuthStrategy());
}
public static AuthStrategy getAuthStrategy(AuthEnumType authEnumType) {
return authStrategyMap.get(authEnumType);
}
}
以上代码仍有不足的地方,当我们需要添加新的登录方式策略时,需要修改策略工厂中静态代码块的代码,违背了开闭原则。
如何避免呢?
Spring扩展ApplicationContextAware
ApplicationContextAware 是一个接口,它允许任何实现了它的类访问到当前的ApplicationContext。这在某些情况下非常有用,比如当一个类需要直接与 Spring 容器交互时。
解决方案
- 使用自定义自定义注解,用户表示具体策略类
- 实现ApplicationContextAware接口,便于访问Spring上下文
- 通过ApplicationContext.getBeanWithAnnotation()方法找到带有标签的类,并构建LoginStrategy映射
代码实现
@Component
public class AuthStrategyAnnotationParseFactory implements ApplicationContextAware {
private ApplicationContext applicationContext;
private final EnumMap<AuthEnumType,AuthStrategy> authEnumTypeAuthStrategyEnumMap = new EnumMap<>(AuthEnumType.class);
public AuthStrategy getAuthStrategy(AuthEnumType authEnumType) {
EnumMap<AuthEnumType, AuthStrategy> authEnumTypeAuthStrategyEnumMap1 = assembleAuthStrategy();
AuthStrategy authStrategy = authEnumTypeAuthStrategyEnumMap1.get(authEnumType);
if (authStrategy == null) {
throw new UnsupportedOperationException(String.format("input type is %s , not null", authEnumType.getValue()));
}
return authStrategy;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 构建AuthStrategy映射
* @return
*/
public EnumMap<AuthEnumType,AuthStrategy> assembleAuthStrategy() {
if (CollUtil.isEmpty(authEnumTypeAuthStrategyEnumMap)){
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Auth.class);
for (Object bean: beansWithAnnotation.values()) {
if (bean instanceof AuthStrategy){
Auth annotation = bean.getClass().getAnnotation(Auth.class);
String description = annotation.description();
authEnumTypeAuthStrategyEnumMap.put(AuthEnumType.fromTypeName(description), (AuthStrategy) bean);
}
}
}
return authEnumTypeAuthStrategyEnumMap;
}
}
实现在运行时动态地创建和注入新的具体策略类的方法还有很多,比如使用自定义@EnableXXXXX的方式配合,FactoryBean和ImportBeanDefinitionRegister进行动态注入
如果还有其他方法可以在发在评论区上一起学习进步!