文章目录 策略模式(行为模式) 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 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;}/*** 参数校验* @return {@code true} 参数校验正确*/private boolean validateParam(Map map) {if (CollectionUtils.isEmpty(map)) {return false;}return true;}
}
3. 基础任务类
public abstract class BaseJobService{private List strategyServiceList;/*** 个性化分发服务 不需要每个子类都实现* 注册策略服务 注册视频聊榜单具体子类列表* @param jobSceneForwardTypes 根据对应的 scene 转发到配置中* @param scene 场景*/protected void doRegisterForward(JobSceneForwardTypes jobSceneForwardTypes, String scene) throws Exception {if(jobSceneForwardTypes == null){System.out.println("注册服务处理转发类 参数校验失败:" + jobSceneForwardTypes);return;}// 根据场景获取对应转发 type 节点List typeList = getForwardTypeList(jobSceneForwardTypes, scene);// 根据 type 节点获取对应 bean 节点List beanList = getForwardBeanList(typeList);// 根据对应 bean 节点实例化对象List strategyServiceList = newInstanceBeanList(beanList);// 内存存储实例化对象setStrategyServiceList(strategyServiceList);}/*** 根据场景获取对应转发 type 节点,遍历的文档 JobSceneForwardConfig.xml* @param jobSceneForwardTypes 根据对应的 scene 转发到配置中* @param scene 场景* @return 文档所有 type 节点*/private List getForwardTypeList(JobSceneForwardTypes jobSceneForwardTypes, String scene)throws Exception {List 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 enumsList = document.selectNodes("/job/enums");// 遍历 JobSceneForwardConfig.xml 下 enums 根据 enumsName 获取对应的 listList 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;}}}// 遍历 JobSceneForwardConfig.xml 下对应的 enums/type 根据 scene-id 获取对应的 listif (!CollectionUtils.isEmpty(typeList)) {for(Element typeElement : typeList){List sceneList = typeElement.selectNodes("list/scene");// 遍历 enums/type 下的每个 scene 根据 scene 获取对应 typeif(!CollectionUtils.isEmpty(sceneList)){for(Element sceneElement : sceneList){String sceneId = getAttribute(sceneElement, "id");System.out.println("根据场景获取对应转发type sceneId:" + sceneId + " scene:" + scene);// 如果id匹配 获取对应typeif(scene != null && (scene).equals(sceneId)){resultList.add(typeElement);break;}}}}}System.out.println("根据场景获取对应转发type:" + resultList);return resultList;}/*** 遍历的文档 JobSceneForwardConfig.xml* @param ScenetypeList JobSceneForwardConfig.xml 所有 type 节点* @return 根据 type 节点获取对应 bean 节点*/private List getForwardBeanList(List ScenetypeList) throws Exception {List 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 monitorTypeList = document.selectNodes("/job/type");// 遍历可使用的 typeList 以及 JobMonitorForwardConfig.xml 匹配对应 beanListif(!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;}/*** 根据对应 bean 节点实例化对象* @param beanList 所有 bean 节点* @return 所有实例化对象*/private List newInstanceBeanList(List beanList) {List 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 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 getStrategyServiceList() {return strategyServiceList;}public void setStrategyServiceList(List strategyServiceList) {this.strategyServiceList = strategyServiceList;}
}
4. 定义策略模式转发的规范
public interface IStrategyService {/*** 通知转发操作* 每一个模块都要重新此动作*/void doNotifyOperation(Map map);
}
5. 代理的第一层
public class StrategyContextService {private IStrategyService strategyService;public StrategyContextService(IStrategyService strategyService) {this.strategyService = strategyService;}/*** 所有的策略服务都必须先执行此操作* 代理层监听到消息,调用此方法*/public void doAction(Map map) {System.out.println("所有的策略服务都执行此操作 map:" + map);if (null == strategyService) {throw new RuntimeException("代理的第一层策略入口服务为空 map:" + map);}strategyService.doNotifyOperation(map);}}
6. 代理的第二层抽象父类:定义视频聊榜单代理规范
public abstract class AbstractVideoRankStrategy implements IStrategyService {/*** 视频聊榜单都会执行此操作*/@Overridepublic void doNotifyOperation(Map map) {System.out.println("视频聊榜单都会执行此操作 map:"+ map);if (!validateParam(map)) {System.out.println("代理的第二层参数校验不正确 map:" + map);return;}doNotifyReward(map);}/*** 参数校验* @return {@code true} 参数校验通过*/protected abstract boolean validateParam(Map map);/*** 延迟到子类进行转发,转发到具体消费者队列*/protected abstract void doNotifyReward(Map map);
}
7. 代理的第二层实现子类
public class ModifyVideoRankHotStrategyImpl extends AbstractVideoRankStrategy {/*** 参数校验* @return {@code true} 参数校验通过*/@Overrideprotected boolean validateParam(Map map) {if (CollectionUtils.isEmpty(map)) {return false;}return true;}/*** 转发到具体消费者队列*/@Overrideprotected void doNotifyReward(Map map) {System.out.println("转发到视频聊热门榜单...");}
}
public class ModifyVideoRankRecommendStrategyImpl extends AbstractVideoRankStrategy {/*** 参数校验* @return {@code true} 参数校验通过*/@Overrideprotected boolean validateParam(Map map) {if (CollectionUtils.isEmpty(map)) {return false;}return true;}/*** 转发到具体消费者队列*/@Overrideprotected void doNotifyReward(Map 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
JobMonitorForwardConfig.xml
10. 策略模式榜单服务接收消息入口测试类
public class RankReceivingEntranceTest {@Testpublic void test() {RankReceivingEntrance rankReceivingEntrance = new RankReceivingEntrance();Map sceneMapLogin = new HashMap<>();sceneMapLogin.put("scene", VideoRankTypes.USER_LOGIN.getType());rankReceivingEntrance.process(sceneMapLogin);System.out.println();Map sceneMapRegister = new HashMap<>();sceneMapRegister.put("scene", VideoRankTypes.USER_REGISTER.getType());rankReceivingEntrance.process(sceneMapRegister);}
}