在开发过程中,经常会遇到判断某个类型字段的多种类型,通常我们会使用if..else if 或者 switch来进行一个个的判断,如果类型越来越多,写的if..else就会越来越多,代码的可读性就会越来越差,时间久了哪怕自己都有点搞不明白了,更不说他人来维护,那么为了解决这一困扰我们可以使用 策略模式 来对代码进行优化。
策略模式的定义与特点
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下。
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
代码实现
以下策略实现为项目中经常会用到的通知业务为例子:
定义通知策略接口
/**
* 消息策略接口
* @author francis
*
*/
public interface INoticeStrategy {
/**
* 执行策略
* @param msg
*/
public void doStrategy(String msg);
}
自定义注解,方便找到具体的策略实现类
/**
* 消息通知类型注解
* @author francis
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoticeType {
NoticeTypeEnum value();
}
消息类型枚举类
/**
*
* @author francis
*
*/
public enum NoticeTypeEnum {
SYSTEM, LIKE, COMMENT;
}
各种通知实现类,每一种通知都需要新建一个类来维护并且继承策略接口
/**
* 系统通知策略
* @author francis
*
*/
@Slf4j
@Component
@NoticeType(NoticeTypeEnum.SYSTEM)
public class SysNoticeStrategyImpl implements INoticeStrategy {
@Override
public void doStrategy(String msg) {
log.info("系统消息:{}", msg);
}
}
/**
* 点赞消息通知策略
* @author francis
*
*/
@Slf4j
@Component
@NoticeType(NoticeTypeEnum.LIKE)
public class LikeNoticeStrategyImpl implements INoticeStrategy {
@Override
public void doStrategy(String msg) {
log.info("点赞消息:{}", msg);
}
}
/**
* 评论消息通知策略
* @author francis
*
*/
@Slf4j
@Component
@NoticeType(NoticeTypeEnum.COMMENT)
public class CommentNoticeStrategyImpl implements INoticeStrategy {
@Override
public void doStrategy(String msg) {
log.info("评论消息:{}", msg);
}
}
定义策略工厂,统一维护各个策略类并且分发消息给指定的实现类
/**
* 通知策略工厂
* @author francis
*/
@Component
public class NoticeStrategyFactory {
private Map<NoticeTypeEnum, INoticeStrategy> strategyMap = new ConcurrentHashMap<>();
/**
* Autowired 会自动注入所有的实现NoticeStrategyFactory的bean <br/>
* 然后获取类上NoticeType注解的值替换掉 bean初始化的名称 组成一个新的strategyMap
* @param strategyMap
*/
@Autowired
public NoticeStrategyFactory(Map<String, INoticeStrategy> strategyMap) {
this.strategyMap.clear();
Set<String> strategyKeys = strategyMap.keySet();
for (String key : strategyKeys) {
Class<? extends INoticeStrategy> clazz = strategyMap.get(key).getClass();
NoticeType annotation = clazz.getAnnotation(NoticeType.class);
if (annotation != null) {
this.strategyMap.put(annotation.value(), strategyMap.get(key));
}
}
}
/**
* 寻找对应得策略处理器
*/
public INoticeStrategy getHandler(NoticeTypeEnum type) {
return strategyMap.get(type);
}
}
大家可以NoticeStrategyFactory 类中打断点调试一下看 map 里是否有所有的实现类。
*******************
注意:如果你的实现类中重写的方法加了事务的,那么这种装配方法就不可用,因为被事务代理的类中获取不到自定义注解。
解决办法:在Spring 中Spring IOC容器给我们提供的一个扩展接口BeanPostProcessor,在这个接口类中给我们提供了两个方法:
// 在bean初始化之前操作
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws
BeansException {
return bean;
}
// 在bean初始化之后操作
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws
BeansException {
return bean;
}
然后我们让策略工厂类 实现BeanPostProcessor 接口 重写里面的postProcessBeforeInitialization方法 在类被事务代理之前获取到实现上的自定义注解:
@Component
public class NoticeStrategyFactory implements BeanPostProcessor{
private Map<NoticeTypeEnum, INoticeStrategy> strategyMap = new ConcurrentHashMap<>();
/**
* 事务代理会影响bean获取其bean上的注解,此处用BeanPostProcessor 在事务代理之前 拿到bean 的注解
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 只对实现了INoticeStrategy的类做操作
if(bean instanceof INoticeStrategy ) {
Class<? extends Object> clazz = bean.getClass();
NoticeType annotation = clazz.getAnnotation(NoticeType.class);
this.strategyMap.put(annotation.value(), (INoticeStrategy)bean);
}
return bean;
}
/**
* 寻找对应得策略处理器
*/
public INoticeStrategy getHandler(NoticeTypeEnum type) {
return strategyMap.get(type);
}
}
以上就是spring 中策略的一种实现(当然,有很多中实现方式),现在我们来写个controller验证一下。
@RestController
@RequestMapping("/strategy")
public class StrategyTestController {
@Autowired
private NoticeStrategyFactory noticeStrateyFactory;
@GetMapping("sendSysNotice")
public void sendSysNotice() {
noticeStrateyFactory.getHandler(NoticeTypeEnum.SYSTEM).doStrategy("有一条系统消息!!");
}
@GetMapping("sendLikeNotice")
public void sendLikeNotice() {
noticeStrateyFactory.getHandler(NoticeTypeEnum.LIKE).doStrategy("xxx给你点赞了!!");
}
@GetMapping("sendCommentNotice")
public void sendCommentNotice() {
noticeStrateyFactory.getHandler(NoticeTypeEnum.COMMENT).doStrategy("xxx评论了你的博客!!");
}
}
分别调用以上接口,控制台打印:
源码传送门: strategy-demo