文章目录
- 策略模式(行为模式)
- 1. 策略模式介绍
- 2. 好处
- 3. 场景案例
- 4. 案例源码
- 1. 代码结构
- 2. 榜单服务接收消息入口
- 3. 基础任务类
- 4. 定义策略模式转发的规范
- 5. 代理的第一层
- 6. 代理的第二层抽象父类:定义视频聊榜单代理规范
- 7. 代理的第二层实现子类
- 8. 枚举
- 9. XML 配置
- 10. 策略模式榜单服务接收消息入口测试类
策略模式(行为模式)
1. 策略模式介绍
- 将一系列同类型的算法放在一起,用一个算法封装起来,由独立于使用的客户端自己选择对应算法使用
- 通常有以下角色:
- 抽象策略(Strategy)类:它是公共接口,各种不同的算法以不同的方式实现这个接口,控制类使用这个接口调用不同的算法
- 具体策略(Concrete Strategy)类:实现了抽象策略接口,提供具体的算法实现
- 控制(Context)类:持有一个策略类的引用,统一提供实现算法的方法,最终给客户端调用
2. 好处
- 替代 if/else 导致的代码混乱
- 隔离不同算法在自己的类中,改动一个算法不会影响其他算法,符合单一职责原则
3. 场景案例
- 分布式消息队列采用策略模式,处理大量类似的榜单维护
- 当前项目分布式队列使用的是点对点通信,即动作发生(如:登录、注册、直播动作、坐等动作、视频聊动作等)时作为生产者发送消息、放入对应消息队列,队列处理程序作为消费者、循环顺序拉取消息队列中的单个消息进行处理
- 我们大量榜单都是实时榜单,榜单更新时过滤条件多而改动频繁,且不同榜单过滤条件不一致
- 视频聊热门榜单需要过滤开直播的用户,用户开直播下榜、关直播上榜
- 当前解决方案
- 用户发生动作时,生产消息到对应榜单队列处理程序,消费者根据过滤条件更新榜单
- 由于条件很多且经常修改,导致每次都要修改很多个生产者,然后重启
- 优化使用策略模式 + 静态代理(根据场景选择转发)+ 模板方法模式
- 改变配置即可改变队列进行不同的推送、且不需要修改与重启任何生产者
- 类似将分布式消息队列的点对点通信改成热加载的发布订阅模式
- 设计思路
- 加代理层,生产者动作触发后发送到代理层,代理层接收到消息再发送到对应消费者处理
- 代理层使用 XML 配置的方式,配置场景(即生产者的动作)绑定处理业务(消费者处理程序)
- 修改条件时,仅需要修改配置,然后重启代理层业务即可
- 代理层注册时将榜单业务初始化到内存,然后监听生产者消息
- 消息先发送到第一层总代理层,然后再根据配置发到第二层具体业务的代理层,通过此代理发消息到具体消费者程序处理
- 整体类图
4. 案例源码
1. 代码结构
├── IStrategyService.java
├── RankReceivingEntrance.java
├── enums
│ ├── JobSceneForwardTypes.java
│ └── VideoRankTypes.java
├── proxy
│ ├── AbstractVideoRankStrategy.java
│ ├── ModifyVideoRankHotStrategyImpl.java
│ ├── ModifyVideoRankRecommendStrategyImpl.java
│ └── StrategyContextService.java
└── register
└── BaseJobService.java
└── resources
└── strategy
├── JobMonitorForwardConfig.xml
└── JobSceneForwardConfig.xml
2. 榜单服务接收消息入口
public class RankReceivingEntrance extends BaseJobService {
public boolean process(Map<String, String> map) {
if (!validateParam(map)) {
System.out.println("消息接收入口参数校验不正确 map:" + map);
return false;
}
System.out.println("进入榜单服务接收消息入口 map:" + map);
String scene = map.getOrDefault("scene", "0");
try {
doRegisterForward(JobSceneForwardTypes.VIDEO_RANK_CONSUMER_TYPES, scene);
doMonitorForward(map);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean validateParam(Map<String,String> map) {
if (CollectionUtils.isEmpty(map)) {
return false;
}
return true;
}
}
3. 基础任务类
public abstract class BaseJobService{
private List<IStrategyService> strategyServiceList;
protected void doRegisterForward(JobSceneForwardTypes jobSceneForwardTypes, String scene) throws Exception {
if(jobSceneForwardTypes == null){
System.out.println("注册服务处理转发类 参数校验失败:" + jobSceneForwardTypes);
return;
}
List<Element> typeList = getForwardTypeList(jobSceneForwardTypes, scene);
List<Element> beanList = getForwardBeanList(typeList);
List<IStrategyService> strategyServiceList = newInstanceBeanList(beanList);
setStrategyServiceList(strategyServiceList);
}
private List<Element> getForwardTypeList(JobSceneForwardTypes jobSceneForwardTypes, String scene)
throws Exception {
List<Element> resultList = new ArrayList<>();
String jsfcFilePath = System.getProperty("user.dir") + "/src/main/resources/strategy/JobSceneForwardConfig.xml";
File file = new File(jsfcFilePath);
if (null == file) {
throw new RuntimeException("JobSceneForwardConfig.xml 文件找不到");
}
Document document = parse(file);
List<Element> enumsList = document.selectNodes("/job/enums");
List<Element> typeList = new ArrayList<>();
if (!CollectionUtils.isEmpty(enumsList)) {
for (Element enumsElement : enumsList) {
String enumsName = getAttribute(enumsElement, "name");
System.out.println("根据场景获取对应转发type enumsName:" + enumsName
+ " jobSceneForwardTypes:" + jobSceneForwardTypes);
if (jobSceneForwardTypes.getType().equals(enumsName)) {
typeList.addAll(enumsElement.selectNodes("list/type"));
break;
}
}
}
if (!CollectionUtils.isEmpty(typeList)) {
for(Element typeElement : typeList){
List<Element> sceneList = typeElement.selectNodes("list/scene");
if(!CollectionUtils.isEmpty(sceneList)){
for(Element sceneElement : sceneList){
String sceneId = getAttribute(sceneElement, "id");
System.out.println("根据场景获取对应转发type sceneId:" + sceneId + " scene:" + scene);
if(scene != null && (scene).equals(sceneId)){
resultList.add(typeElement);
break;
}
}
}
}
}
System.out.println("根据场景获取对应转发type:" + resultList);
return resultList;
}
private List<Element> getForwardBeanList(List<Element> ScenetypeList) throws Exception {
List<Element> resultList = new ArrayList<>();
String jmfcFilePath = System.getProperty("user.dir") + "/src/main/resources/strategy/JobMonitorForwardConfig.xml";
File file = new File(jmfcFilePath);
if (null == file) {
throw new RuntimeException("JobMonitorForwardConfig.xml 文件找不到");
}
Document document = parse(file);
List<Element> monitorTypeList = document.selectNodes("/job/type");
if(!CollectionUtils.isEmpty(ScenetypeList) && !CollectionUtils.isEmpty(monitorTypeList)){
for(Element sceneTypeElement : ScenetypeList){
for(Element monitorTypeElement : monitorTypeList){
String sceneTypeName = getAttribute(sceneTypeElement, "name");
String monitorTypeName = getAttribute(monitorTypeElement, "name");
System.out.println("根据type节点获取对应bean节点 sceneTypeName:" + sceneTypeName
+ " monitorTypeName:" + monitorTypeName);
if(sceneTypeName != null && sceneTypeName.equals(monitorTypeName)){
resultList.addAll(monitorTypeElement.selectNodes("list/bean"));
break;
}
}
}
}
System.out.println("根据type节点获取对应bean节点:" + resultList);
return resultList;
}
private <T> List<T> newInstanceBeanList(List<Element> beanList) {
List<T> resultList = new ArrayList<>();
if(!CollectionUtils.isEmpty(beanList)){
for(Element beanElement : beanList){
String className = getAttribute(beanElement, "class");
if(!StringUtils.isEmpty(className)) {
try {
Class clazz = Class.forName(className);
resultList.add((T) clazz.newInstance());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
System.out.println("根据对应bean节点实例化对象 className:" + className);
}
}
System.out.println("根据对应bean节点实例化对象:" + resultList);
return resultList;
}
protected void doMonitorForward(Map<String, String> map) {
System.out.println("开启监听 执行具体策略操作 :" + strategyServiceList);
if(!CollectionUtils.isEmpty(strategyServiceList)){
for(IStrategyService strategyService : strategyServiceList){
StrategyContextService strategyContextService = new StrategyContextService(strategyService);
strategyContextService.doAction(map);
}
}
}
private static Document parse(File file) throws DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(file);
return document;
}
private static String getAttribute(Element ele, String attrName) {
Attribute attribute = ele.attribute(attrName);
return attribute == null ? null : attribute.getValue();
}
public List<IStrategyService> getStrategyServiceList() {
return strategyServiceList;
}
public void setStrategyServiceList(List<IStrategyService> strategyServiceList) {
this.strategyServiceList = strategyServiceList;
}
}
4. 定义策略模式转发的规范
public interface IStrategyService {
void doNotifyOperation(Map<String, String> map);
}
5. 代理的第一层
public class StrategyContextService {
private IStrategyService strategyService;
public StrategyContextService(IStrategyService strategyService) {
this.strategyService = strategyService;
}
public void doAction(Map<String, String> map) {
System.out.println("所有的策略服务都执行此操作 map:" + map);
if (null == strategyService) {
throw new RuntimeException("代理的第一层策略入口服务为空 map:" + map);
}
strategyService.doNotifyOperation(map);
}
}
6. 代理的第二层抽象父类:定义视频聊榜单代理规范
public abstract class AbstractVideoRankStrategy implements IStrategyService {
@Override
public void doNotifyOperation(Map<String, String> map) {
System.out.println("视频聊榜单都会执行此操作 map:"+ map);
if (!validateParam(map)) {
System.out.println("代理的第二层参数校验不正确 map:" + map);
return;
}
doNotifyReward(map);
}
protected abstract boolean validateParam(Map<String, String> map);
protected abstract void doNotifyReward(Map<String, String> map);
}
7. 代理的第二层实现子类
public class ModifyVideoRankHotStrategyImpl extends AbstractVideoRankStrategy {
@Override
protected boolean validateParam(Map<String, String> map) {
if (CollectionUtils.isEmpty(map)) {
return false;
}
return true;
}
@Override
protected void doNotifyReward(Map<String, String> map) {
System.out.println("转发到视频聊热门榜单...");
}
}
public class ModifyVideoRankRecommendStrategyImpl extends AbstractVideoRankStrategy {
@Override
protected boolean validateParam(Map<String, String> map) {
if (CollectionUtils.isEmpty(map)) {
return false;
}
return true;
}
@Override
protected void doNotifyReward(Map<String, String> map) {
System.out.println("转发到视频聊推荐榜单...");
}
}
8. 枚举
- 根据对应的 scene 转发到配置中
- 视频聊榜单生产者业务类型
@Getter
@ToString
public enum JobSceneForwardTypes {
VIDEO_RANK_CONSUMER_TYPES("VideoRankConsumerTypes", "视频聊榜单处理转发"),
;
private String type;
private String desc;
JobSceneForwardTypes(String type, String desc){
this.type = type;
this.desc = desc;
}
}
@Getter
@ToString
public enum VideoRankTypes {
USER_LOGIN("1", "用户登录"),
USER_REGISTER("2", "注册"),
;
private String type;
private String desc;
VideoRankTypes(String type, String desc) {
this.type = type;
this.desc = desc;
}
}
9. XML 配置
- JobSceneForwardConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<job>
<!--注意需要重启此枚举对应的方法:strategy.enums.JobSceneForwardTypes-->
<!-- 视频聊榜单转发处理 -->
<enums name="VideoRankConsumerTypes">
<list>
<!--name 绑定 JobMonitorForwardConfig.xml 的 name 字段-->
<type name="MODIFY_VIDEO_RANK_RECOMMEND" desc="更新视频聊推荐榜单">
<!--视频聊榜单场景枚举:strategy.enums.VideoRankTypes-->
<list>
<scene id="1" desc="USER_LOGIN-用户登录"/>
<scene id="2" desc="USER_REGISTER-用户注册"/>
</list>
</type>
<type name="MODIFY_VIDEO_RANK_HOT" desc="更新视频聊热门榜单">
<list>
<scene id="1" desc="USER_LOGIN-用户登录"/>
</list>
</type>
</list>
</enums>
</job>
- JobMonitorForwardConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<job>
<!--更新视频聊推荐榜单-->
<!--name 绑定 JobSceneForwardConfig.xml 的 name 字段-->
<type name="MODIFY_VIDEO_RANK_RECOMMEND">
<list>
<bean class="strategy.proxy.ModifyVideoRankRecommendStrategyImpl" desc="更新视频聊推荐榜单" />
</list>
</type>
<!--更新视频聊热门榜单-->
<type name="MODIFY_VIDEO_RANK_HOT">
<list>
<bean class="strategy.proxy.ModifyVideoRankHotStrategyImpl" desc="更新视频聊热门榜单" />
</list>
</type>
</job>
10. 策略模式榜单服务接收消息入口测试类
public class RankReceivingEntranceTest {
@Test
public void test() {
RankReceivingEntrance rankReceivingEntrance = new RankReceivingEntrance();
Map<String, String> sceneMapLogin = new HashMap<>();
sceneMapLogin.put("scene", VideoRankTypes.USER_LOGIN.getType());
rankReceivingEntrance.process(sceneMapLogin);
System.out.println();
Map<String, String> sceneMapRegister = new HashMap<>();
sceneMapRegister.put("scene", VideoRankTypes.USER_REGISTER.getType());
rankReceivingEntrance.process(sceneMapRegister);
}
}