策略模式详解
创始人
2024-05-30 22:34:27

文章目录

  • 策略模式(行为模式)
    • 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);}
}

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...