规则决策树
2024/12/19大约 8 分钟
规则决策树
1、需求
一棵树上有4颗果实,张三根据条件获取到果实B,其中判断条件为,摘果实的必须为男并且年龄必须大于25
2、屎山
2.1 错误案例
public class EngineController {
private Logger logger = LoggerFactory.getLogger(EngineController.class);
public String process(final String userId, final String userSex, final int userAge) {
logger.info("ifelse实现方式判断用户结果。userId:{} userSex:{} userAge:{}", userId, userSex, userAge);
if ("man".equals(userSex)) {
if (userAge < 25) {
return "果实A";
}
if (userAge >= 25) {
return "果实B";
}
}
if ("woman".equals(userSex)) {
if (userAge < 25) {
return "果实C";
}
if (userAge >= 25) {
return "果实D";
}
}
return null;
}
}
测试:
@Test
public void test_EngineController() {
EngineController engineController = new EngineController();
String process = engineController.process("Oli09pLkdjh", "man", 29);
logger.info("测试结果:{}", process);
}
2.2 分析
由于屎山中大量的if..else堆叠,代码不易扩展性,后期如果添加新的需求,比如要求身高为175以上,则需要在原有代码上更改,违背开闭原则
3、组合模式实现规则决策树
为了解决屎山带来的代码扩展性低,耦合度高的问题,使用组合模式实现决策引擎,并动态配置树节点的子叶与果实,实现动态过滤
3.1 树的定义
树包含树根节点,树节点信息,树线信息(使节点与节点之间相互连接)
3.1.1 树根节点定义
package top.tec.domain.model.vo;
import lombok.Data;
/**
* @author: hujincheng
* @description: TreeRoot 树根信息
* @create: 2024-06-26 23:28
*/
@Data
public class TreeRoot {
/**
* 规则树ID
*/
private Long treeId;
/**
* 树根节点ID
*/
private Long treeRootNodeId;
/**
* 规则树名称
*/
private String treeName;
}
3.1.2 树节点定义
package top.tec.domain.model.vo;
import lombok.Data;
import java.util.List;
/**
* @author: hujincheng
* @description: TreeNode 规则树节点信息
* @create: 2024-06-26 23:30
*/
@Data
public class TreeNode {
/**
* 规则树ID
*/
private Long treeId;
/**
* 规则树节点ID
*/
private Long treeNodeId;
/**
* 节点类型 [ 叶子: 1 , 果实: 2 ]
*/
private Integer nodeType;
/**
* 节点值(果实) [只需要存储果实的值即可:nodeType=2]
*/
private String nodeValue;
/**
* 规则Key(对应的过滤器名称)
*/
private String ruleKey;
/**
* 规则描述
*/
private String ruleDesc;
/**
* 节点链路
*/
private List<TreeNodeLink> treeNodeLinkList;
}
3.1.3树线信息定义
package top.tec.domain.model.vo;
import lombok.Data;
/**
* @author: hujincheng
* @description: TreeNodeLink 规则树线信息
* @create: 2024-06-27 00:02
*/
@Data
public class TreeNodeLink {
/**
* 节点From
*/
private Long nodeIdFrom;
/**
* 节点To
*/
private Long nodeIdTo;
/**
* 限定类型;
* 1:=;
* 2:>;
* 3:<;
* 4:>=;
* 5<=;
* 6:enum[枚举范围]
*/
private Integer ruleLimitType;
/**
* 限定值
*/
private String ruleLimitValue;
}
3.1.4 规则树聚合定义
package top.tec.domain.model.aggregates;
import lombok.Builder;
import lombok.Data;
import top.tec.domain.model.vo.TreeNode;
import top.tec.domain.model.vo.TreeRoot;
import java.util.Map;
/**
* @author: hujincheng
* @description: TreeRich 规则树聚合
* @create: 2024-06-27 00:06
*/
@Data
@Builder
public class TreeRich {
/**
* 树根信息
*/
private TreeRoot treeRoot;
/**
* (所有节点)树节点ID -> 子节点
*/
private Map<Long, TreeNode> treeNodeMap;
}
3.1.5 解释
树根节点代表整颗树的首节点位置,类似二叉树,在每个节点下通过树线连接
3.2 规则引擎的定义
3.2.1 为什么要定义规则引擎
为了外部调用规则树实现规则的决策
测试时代码:
//定义规则树引擎
IEngine treeEngineHandle = new TreeEngineHandle();
//执行规则引擎决策
EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);
3.2.2 定义引擎统一返回
package top.tec.domain.model.vo;
import lombok.Builder;
import lombok.Data;
/**
* @author: hujincheng
* @description: EngineResult 决策结果
* @create: 2024-06-27 00:10
*/
@Data
@Builder
public class EngineResult {
/**
* 执行结果
*/
private boolean isSuccess;
/**
* 用户ID
*/
private String userId;
/**
* 规则树ID
*/
private Long treeId;
/**
* 果实节点ID
*/
private Long nodeId;
/**
* 果实节点值
*/
private String nodeValue;
}
3.2.2 定义规则引擎接口
package top.tec.domain.service.engine;
import top.tec.domain.model.aggregates.TreeRich;
import top.tec.domain.model.vo.EngineResult;
import java.util.Map;
/**
* @author: hujincheng
* @description: IEngine 规则树引擎
* @create: 2024-06-27 00:08
*/
public interface IEngine {
/**
* 处理引擎
*/
EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String,String> decisionMatter);
}
3.2.3 定义引擎配置
由于当前环境为maven环境非String容易中,故配置静态代码块加载条件过滤器
package top.tec.domain.service.engine;
import top.tec.domain.service.logic.LogicFilter;
import top.tec.domain.service.logic.impl.SGFilter;
import top.tec.domain.service.logic.impl.UserAgeFilter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: hujincheng
* @description: EngineConfig 加载配置过滤器
* @create: 2024-06-27 00:17
*/
public class EngineConfig {
protected static Map<String, LogicFilter> filterMap = new ConcurrentHashMap<>();
static {
filterMap.put("SGFilter", new SGFilter());
filterMap.put("userAge", new UserAgeFilter());
}
}
3.2.4 定义规则引擎抽象实现(实现统一决策)
根据定义的线规则决策器决策是否满足条件,从而获取到满足条件的决策结果
package top.tec.domain.service.engine;
import top.tec.domain.model.aggregates.TreeRich;
import top.tec.domain.model.vo.EngineResult;
import top.tec.domain.model.vo.TreeNode;
import top.tec.domain.model.vo.TreeRoot;
import top.tec.domain.service.logic.LogicFilter;
import java.util.Map;
/**
* @author: hujincheng
* @description: EngineBase
* @create: 2024-06-27 00:16
*/
public abstract class EngineBase extends EngineConfig implements IEngine {
@Override
public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);
protected TreeNode engineDecisionMaker(TreeRich treeRich, Map<String, String> decisionMatter) {
TreeRoot treeRoot = treeRich.getTreeRoot();
//获取决策树根节点ID
Long rootNodeId = treeRoot.getTreeRootNodeId();
//获取决策树节点列表
Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
//判断是否是叶子节点
while (null != treeNodeInfo && treeNodeInfo.getNodeType().equals(1)) {
String ruleKey = treeNodeInfo.getRuleKey();
//获取线规则决策器
LogicFilter logicFilter = filterMap.get(ruleKey);
if (null == logicFilter) {
throw new RuntimeException("规则ruleKey:" + ruleKey + "对应的过滤器未定义");
}
String matterValue = logicFilter.matterValue(decisionMatter);
Long nextNodeId = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
treeNodeInfo = treeNodeMap.get(nextNodeId);
System.out.println("决策树引擎=>" + treeRoot.getTreeName() + "treeNode:" + treeNodeInfo.getTreeNodeId() + " ruleKey:" + ruleKey + " matterValue:" + matterValue);
}
return treeNodeInfo;
}
}
3.2.5 定义规则引擎默认具体实现
具体实现中目前仅处理决策结果返回
package top.tec.domain.service.engine.impl;
import top.tec.domain.model.aggregates.TreeRich;
import top.tec.domain.model.vo.EngineResult;
import top.tec.domain.model.vo.TreeNode;
import top.tec.domain.service.engine.EngineBase;
import java.util.Map;
/**
* @author: hujincheng
* @description: TreeEngineHandle 决策树引擎实现
* @create: 2024-06-27 00:18
*/
public class TreeEngineHandle extends EngineBase {
@Override
public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
//处理决策
TreeNode treeRoot = engineDecisionMaker(treeRich,decisionMatter);
//返回决策结果
return EngineResult.builder()
.isSuccess(Boolean.TRUE)
.userId(userId)
.treeId(treeId)
.nodeId(treeRoot.getTreeNodeId())
.nodeValue(treeRoot.getNodeValue())
.build();
}
}
3.3 线规则决策过滤器
相信引擎定义完成之后,会存在引擎配置中的线规则决策过滤器的疑问,接下来,他来了
3.3.1 定义统一线规则过滤器核心接口
方便扩展其他规则过滤器
package top.tec.domain.service.logic;
import top.tec.domain.model.vo.TreeNodeLink;
import java.util.List;
import java.util.Map;
/**
* @author: hujincheng
* @description: LogicFilter 过滤器
* @create: 2024-06-27 00:31
*/
public interface LogicFilter {
/**
* 逻辑决策器
*
* @param matterValue 决策值
* @param treeNodeLineInfoList 决策的线节点
* @return 下一个节点Id
*/
Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);
/**
* 获取决策值
*
* @param decisionMatter 决策物料
* @return 决策值
*/
String matterValue(Map<String, String> decisionMatter);
}
3.3.2 定义线规则过滤器抽象实现
规则过滤器中核心过滤规则在抽象抽象实现中实现,是为了扩展其他过滤器时不需要过多关注决断过滤过程,扩展时仅需提供需要决断的字段即可
package top.tec.domain.service.logic;
import top.tec.domain.model.vo.TreeNodeLink;
import java.util.List;
import java.util.Map;
/**
* @author: hujincheng
* @description: BaseLogic 过滤逻辑实现
* @create: 2024-06-27 00:36
*/
public abstract class BaseLogic implements LogicFilter {
@Override
public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
//根据线信息节点决断,返回下个节点Id
for (TreeNodeLink nodeLine : treeNodeLinkList) {
if (decisionLogic(matterValue, nodeLine)) {
return nodeLine.getNodeIdTo();
}
}
throw new RuntimeException("所有线都不匹配当前matterValue: " + matterValue);
//return 0L;
}
/**
* 具体决断
*/
private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
switch (nodeLink.getRuleLimitType()) {
case 1:
return matterValue.equals(nodeLink.getRuleLimitValue());
case 2:
return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
case 3:
return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
case 4:
return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
case 5:
return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
default:
return false;
}
}
@Override
public abstract String matterValue(Map<String, String> decisionMatter);
}
3.3.3 定义具体过滤节点实现规则
3.3.3.1 性别过滤
package top.tec.domain.service.logic.impl;
import top.tec.domain.service.logic.BaseLogic;
import java.util.Map;
public class UserGenderFilter extends BaseLogic {
@Override
public String matterValue(Map<String, String> decisionMatter) {
return decisionMatter.get("gender");
}
}
3.3.3.2 年龄过滤
package top.tec.domain.service.logic.impl;
import top.tec.domain.service.logic.BaseLogic;
import java.util.Map;
public class UserAgeFilter extends BaseLogic {
@Override
public String matterValue(Map<String, String> decisionMatter) {
return decisionMatter.get("age");
}
}
3.3.3.3 身高过滤
package top.tec.domain.service.logic.impl;
import top.tec.domain.service.logic.BaseLogic;
import java.util.Map;
/**
* @author: hujincheng
* @description: SGFilter 身高
* @create: 2024-06-26 23:17
*/
public class SGFilter extends BaseLogic {
@Override
public String matterValue(Map<String, String> decisionMatter) {
return decisionMatter.get("sg");
}
}
3.4 测试
3.4.1 定义决策树,以及相关线决策器上使用的相关规则,以及叶子节点与果实节点
private TreeRich treeRich;
@Before
public void init() {
// 节点:1
TreeNode treeNode_01 = new TreeNode();
treeNode_01.setTreeId(10001L);
treeNode_01.setTreeNodeId(1L);
treeNode_01.setNodeType(1);
treeNode_01.setNodeValue(null);
treeNode_01.setRuleKey("SGFilter");
treeNode_01.setRuleDesc("身高");
// 链接:1->11
TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
treeNodeLink_11.setNodeIdFrom(1L);
treeNodeLink_11.setNodeIdTo(11L);
treeNodeLink_11.setRuleLimitType(3);
treeNodeLink_11.setRuleLimitValue("175");
// 链接:1->12
TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
treeNodeLink_12.setNodeIdFrom(1L);
treeNodeLink_12.setNodeIdTo(12L);
treeNodeLink_12.setRuleLimitType(5);
treeNodeLink_12.setRuleLimitValue("175");
List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
treeNodeLinkList_1.add(treeNodeLink_11);
treeNodeLinkList_1.add(treeNodeLink_12);
treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);
// 节点:11
TreeNode treeNode_11 = new TreeNode();
treeNode_11.setTreeId(10001L);
treeNode_11.setTreeNodeId(11L);
treeNode_11.setNodeType(1);
treeNode_11.setNodeValue(null);
treeNode_11.setRuleKey("userAge");
treeNode_11.setRuleDesc("用户年龄");
// 链接:11->111
TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
treeNodeLink_111.setNodeIdFrom(11L);
treeNodeLink_111.setNodeIdTo(111L);
treeNodeLink_111.setRuleLimitType(3);
treeNodeLink_111.setRuleLimitValue("25");
// 链接:11->112
TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
treeNodeLink_112.setNodeIdFrom(11L);
treeNodeLink_112.setNodeIdTo(112L);
treeNodeLink_112.setRuleLimitType(5);
treeNodeLink_112.setRuleLimitValue("25");
List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
treeNodeLinkList_11.add(treeNodeLink_111);
treeNodeLinkList_11.add(treeNodeLink_112);
treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);
// 节点:12
TreeNode treeNode_12 = new TreeNode();
treeNode_12.setTreeId(10001L);
treeNode_12.setTreeNodeId(12L);
treeNode_12.setNodeType(1);
treeNode_12.setNodeValue(null);
treeNode_12.setRuleKey("userAge");
treeNode_12.setRuleDesc("用户年龄");
// 链接:12->121
TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
treeNodeLink_121.setNodeIdFrom(12L);
treeNodeLink_121.setNodeIdTo(121L);
treeNodeLink_121.setRuleLimitType(3);
treeNodeLink_121.setRuleLimitValue("25");
// 链接:12->122
TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
treeNodeLink_122.setNodeIdFrom(12L);
treeNodeLink_122.setNodeIdTo(122L);
treeNodeLink_122.setRuleLimitType(5);
treeNodeLink_122.setRuleLimitValue("25");
List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
treeNodeLinkList_12.add(treeNodeLink_121);
treeNodeLinkList_12.add(treeNodeLink_122);
treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);
// 节点:111
TreeNode treeNode_111 = new TreeNode();
treeNode_111.setTreeId(10001L);
treeNode_111.setTreeNodeId(111L);
treeNode_111.setNodeType(2);
treeNode_111.setNodeValue("果实A");
// 节点:112
TreeNode treeNode_112 = new TreeNode();
treeNode_112.setTreeId(10001L);
treeNode_112.setTreeNodeId(112L);
treeNode_112.setNodeType(2);
treeNode_112.setNodeValue("果实B");
// 节点:121
TreeNode treeNode_121 = new TreeNode();
treeNode_121.setTreeId(10001L);
treeNode_121.setTreeNodeId(121L);
treeNode_121.setNodeType(2);
treeNode_121.setNodeValue("果实C");
// 节点:122
TreeNode treeNode_122 = new TreeNode();
treeNode_122.setTreeId(10001L);
treeNode_122.setTreeNodeId(122L);
treeNode_122.setNodeType(2);
treeNode_122.setNodeValue("果实D");
// 树根
TreeRoot treeRoot = new TreeRoot();
treeRoot.setTreeId(10001L);
treeRoot.setTreeRootNodeId(1L);
treeRoot.setTreeName("规则决策树");
Map<Long, TreeNode> treeNodeMap = new HashMap<>();
treeNodeMap.put(1L, treeNode_01);
treeNodeMap.put(11L, treeNode_11);
treeNodeMap.put(12L, treeNode_12);
treeNodeMap.put(111L, treeNode_111);
treeNodeMap.put(112L, treeNode_112);
treeNodeMap.put(121L, treeNode_121);
treeNodeMap.put(122L, treeNode_122);
treeRich = TreeRich.builder().treeRoot(treeRoot).treeNodeMap(treeNodeMap).build();
}
3.4.2 执行决策
@Test
public void test_tree() {
IEngine treeEngineHandle = new TreeEngineHandle();
/**
* 测试数据
* 果实A:gender=man、age=22
* 果实B:gender=man、age=29
* 果实C:gender=woman、age=22
* 果实D:gender=woman、age=29
*/
Map<String, String> decisionMatter = new HashMap<>();
//decisionMatter.put("gender", "man");
decisionMatter.put("age", "29");
decisionMatter.put("sg", "170");
EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);
System.out.println("测试结果" + result.toString());
}
3.4.3 决策结果
决策树引擎=>规则决策树treeNode:11 ruleKey:SGFilter matterValue:170
决策树引擎=>规则决策树treeNode:112 ruleKey:userAge matterValue:29
测试结果EngineResult(isSuccess=true, userId=Oli09pLkdjh, treeId=10001, nodeId=112, nodeValue=果实B)
4、优点
使用决策树解决大量if..else..堆积问题,拥有高扩展性,在扩展时,仅须关注规则树的定义与决策参数,线信息节点过滤器,引擎引入节点过滤器即可,不需要对原代码更改,代码健壮性更见,不违背开闭原则,不会向if..else堆叠之后修改这导致另外地方不可用问题