策略模式(Strategy Pattern:Define a family of algorithms,encapsulate each one,and make them interchangeable.)中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费劲,其实就是定义一组通用算法的上层接口,各个算法实现类实现该算法接口,封装模块使用类似于 Context 的概念,Context 暴漏一组接口,Context 内部接口委托到抽象算法层。
策略模式的通用类图如下:
包含的角色如下:
假设我们有个积分发放功能,需要根据不同的积分类型来发分,我们的积分类型假设如下:
/**
* 积分类型枚举
*/
public enum PointTypeEnum {
COURSE_LEARN,
COURSE_COMMENT,
EBOOK_READ,
LIVE_WATCH,
}
然后我们实现一个发放积分的方法:
/**
* 指定积分类型,发分
* @param pointTypeEnum 积分类型
* @param points 要发的积分
*/
private static void sendPoint(PointTypeEnum pointTypeEnum, Integer points) {
switch (pointTypeEnum) {
case COURSE_COMMENT:
log.info("评论课程,获得 {} 积分", points);
break;
case COURSE_LEARN:
log.info("学习课程,获得 {} 积分", points);
break;
case EBOOK_READ:
log.info("阅读电子书,获得 {} 积分", points);
break;
case LIVE_WATCH:
log.info("观看直播,获得 {} 积分", points);
break;
default:
break;
}
}
这里只举例了 4 种积分类型,实际情况可能并不止这么多,而且每种积分发放逻辑都不相同,如果一味的在 switch case 中堆代码是肯定不行,根据我们的设计原则,尽量保证高内聚、低耦合,很显然这种方式是不可取的。
使用策略模式优化下上面的代码,首先我们需要一个策略接口,即积分发放接口:
/**
* 积分发放接口,各个积分类型需要实现该接口,自定义发放逻辑
*/
public interface PointSendInterface {
/**
* 发放积分
* @param points 要发放的积分
*/
void sendPoint(Integer points);
/**
* 积分类型
* @return
*/
PointTypeEnum sendType();
}
然后不同的积分类型需要实现该接口,以便自定义其积分发放规则:
@Slf4j
@Service
public class CourseCommentPointSend implements PointSendInterface {
@Override
public void sendPoint(Integer points) {
log.info("评论课程,获得 {} 积分", points);
}
@Override
public PointTypeEnum sendType() {
return PointTypeEnum.COURSE_COMMENT;
}
}
@Slf4j
@Service
public class CourseLearnPointSend implements PointSendInterface {
@Override
public void sendPoint(Integer points) {
log.info("学习课程,获得 {} 积分", points);
}
@Override
public PointTypeEnum sendType() {
return PointTypeEnum.COURSE_LEARN;
}
}
@Slf4j
@Service
public class EbookReadPointSend implements PointSendInterface {
@Override
public void sendPoint(Integer points) {
log.info("阅读电子书,获得 {} 积分", points);
}
@Override
public PointTypeEnum sendType() {
return PointTypeEnum.EBOOK_READ;
}
}
@Slf4j
@Service
public class LiveWatchPointSend implements PointSendInterface {
@Override
public void sendPoint(Integer points) {
log.info("观看直播,获得 {} 积分", points);
}
@Override
public PointTypeEnum sendType() {
return PointTypeEnum.LIVE_WATCH;
}
}
最后我们要实现一个积分发放的 Context 给业务方使用,大致如下:
/**
* 积分发放类
*/
@Component
public class PointSendContext {
@Autowired
private List<PointSendInterface> sendStrategyList;
private final Map<PointTypeEnum, PointSendInterface> SEND_STRATEGY_MAP = new HashMap<>();
@PostConstruct
public void init() {
sendStrategyList.forEach(strategy -> {
SEND_STRATEGY_MAP.put(strategy.sendType(), strategy);
});
}
/**
* 发放积分
* @param pointTypeEnum 积分类型
* @param points 要发的分
*/
public void sendPoint(PointTypeEnum pointTypeEnum, Integer points) {
PointSendInterface sendStrategy = SEND_STRATEGY_MAP.get(pointTypeEnum);
// 调用具体实现类发放积分
sendStrategy.sendPoint(points);
}
}
这里我们通过 @Autowired 自动注入 PointSendInterface 接口的所有实现类 sendStrategyList,然后通过 @PostConstruct 的初始化方法将每一个实现类组装成 SEND_STRATEGY_MAP 对象以便后续直接通过 pointType 可以映射出来该 pointType 对应的发分方法。调用的话就比较简单了,没有了 switch case 的冗长:
@SpringBootTest
class PointSendContextTest {
@Autowired
private PointSendContext pointSendContext;
@Test
void sendPoint() {
pointSendContext.sendPoint(PointTypeEnum.COURSE_LEARN, 20);
}
}