策略模式详解

文章目录

  • 策略模式(行为模式)
    • 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 配置的方式,配置场景(即生产者的动作)绑定处理业务(消费者处理程序)
    • 修改条件时,仅需要修改配置,然后重启代理层业务即可
    • 代理层注册时将榜单业务初始化到内存,然后监听生产者消息
    • 消息先发送到第一层总代理层,然后再根据配置发到第二层具体业务的代理层,通过此代理发消息到具体消费者程序处理
  • 整体类图
    策略模式详解_第1张图片

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;
    }

    /**
     * 参数校验
     * @return {@code 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;

	/**
	 * 个性化分发服务 不需要每个子类都实现
	 * 注册策略服务 注册视频聊榜单具体子类列表
	 * @param jobSceneForwardTypes 根据对应的 scene 转发到配置中
	 * @param scene 场景
	 */
	protected void doRegisterForward(JobSceneForwardTypes jobSceneForwardTypes, String scene) throws Exception {
		if(jobSceneForwardTypes == null){
			System.out.println("注册服务处理转发类 参数校验失败:" + jobSceneForwardTypes);
			return;
		}
		// 根据场景获取对应转发 type 节点
		List<Element> typeList = getForwardTypeList(jobSceneForwardTypes, scene);

		// 根据 type 节点获取对应 bean 节点
		List<Element> beanList = getForwardBeanList(typeList);

		// 根据对应 bean 节点实例化对象
		List<IStrategyService> strategyServiceList = newInstanceBeanList(beanList);

		// 内存存储实例化对象
		setStrategyServiceList(strategyServiceList);
	}

	/**
	 * 根据场景获取对应转发 type 节点,遍历的文档 JobSceneForwardConfig.xml
	 * @param jobSceneForwardTypes 根据对应的 scene 转发到配置中
	 * @param scene 场景
	 * @return 文档所有 type 节点
	 */
	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");

		// 遍历 JobSceneForwardConfig.xml 下 enums 根据 enumsName 获取对应的 list
		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;
				}
			}
		}

		// 遍历 JobSceneForwardConfig.xml 下对应的 enums/type 根据 scene-id 获取对应的 list
		if (!CollectionUtils.isEmpty(typeList)) {
			for(Element typeElement : typeList){

				List<Element> sceneList = typeElement.selectNodes("list/scene");
				// 遍历 enums/type 下的每个 scene 根据 scene 获取对应 type
				if(!CollectionUtils.isEmpty(sceneList)){
					for(Element sceneElement : sceneList){

						String sceneId = getAttribute(sceneElement, "id");
						System.out.println("根据场景获取对应转发type sceneId:" + sceneId + " scene:" + scene);

						// 如果id匹配 获取对应type
						if(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<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");

		// 遍历可使用的 typeList 以及 JobMonitorForwardConfig.xml 匹配对应 beanList
		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;
	}

	/**
	 * 根据对应 bean 节点实例化对象
	 * @param beanList 所有 bean 节点
	 * @return 所有实例化对象
	 */
	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);
    }

    /**
     * 参数校验
     * @return {@code true} 参数校验通过
     */
    protected abstract boolean validateParam(Map<String, String> map);

    /**
     * 延迟到子类进行转发,转发到具体消费者队列
     */
    protected abstract void doNotifyReward(Map<String, String> map);
}

7. 代理的第二层实现子类

  • 转发到视频聊热门榜单
  • 转发到视频聊推荐榜单
public class ModifyVideoRankHotStrategyImpl extends AbstractVideoRankStrategy {

    /**
     * 参数校验
     * @return {@code true} 参数校验通过
     */
    @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 {

    /**
     * 参数校验
     * @return {@code true} 参数校验通过
     */
    @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);
    }
}

你可能感兴趣的:(设计模式,策略模式,java,设计模式)