画个图如下
营销决策树.png
(1)初看可能会认为根据站点建立一个领域对象,根据用户等级建立一个领域对象,然后进行组合?
但细想,我们怎么能够穷举所有的具体规则和对象呢?,万一以后还需要根据新的标签比如用户性别,年龄,职业等去建立规则,我们岂不是要建立一堆的对象?
所以我们建模应该面向抽象而不是具体
(2)那这里的抽象又是指的是什么?
我们可以看到,该系统的 核心诉求是能根据不同的规则,拿到对应的结果
进一步的细分诉求
(1)规则,结果需要可配置
(2)规则可随意扩展(可以随时增删改规则),随意组合(即可以调整顺序)
(3)规则可方便管理(规则的可视化以及方便的目录管理,我一眼就知道这个规则是干什么的)
(4)规则可以复用,(规则和规则之间要独立解耦)
(3)根据上面的需求我们来设计对应的数据结构和算法?
可以随意配置编排 ->数据结构我们自然想到 链表 ,设计模式我们自然想到 责任链模式
可复用 ->我们自然想到 模板模式
方便管理可视化 ->我们自然想到 决策树 结构
(4)那决策树就是我们的一个 核心领域对象 ,它应该包含哪些细分?
节点TreeNode
a.节点类型
nodeType:根节点,叶子节点,果实节点
b.节点对应的值
nodeValue
c.节点的id
treeNodeId
d.节点对应的处理器类型和描述
ruleKey,ruleDesc
e.节点对应的边
List<TreeNodeLink> treeNodeLinkList
f.节点所属的树id
treeId
节点对应的边TreeNodeLink
a.边的起始和终止节点id
fromNodeId,toNodeId
b.边上面的判断规则,只有满足判断规则,才能从起始走到终止
ruleLimitType,ruleLimitValue
树节点TreeNode
public class TreeNode { /* *所属的树id */ private Long treeId; /* *节点类型 根节点 叶子节点 果实节点 */ private Integer nodeType; /* *节点id */ private Long treeNodeId; /* * 节点所对应的值 一般只有果实节点才会有值
*/
private String nodeValue;
/*
*节点所对应的处理器类型
*/
private String ruleKey;
/*
*节点所对应的处理器描述
*/
private String ruleDesc; /* *节点所对应的边 */ private List<TreeNodeLink> treeNodeLinkList;}边TreeNodeLink
public class TreeNodeLink { /* * 对应的起始节点
*/
private Long nodeIdFrom;
/*
* 对应的终止节点
*/
private Long nodeIdTo;
/*
* 对应的规则判断类型 大于/小于/大于等于/小于等于/等于/不等于
*/
private Integer ruleLimitType;
/*
* 对应的规则判断值
*/
private String ruleLimitValue;
}根节点TreeRoot
public class TreeRoot { /* *根节点Id */ private Long treeRootNodeId; /* *根节点名称 */ private String treeName; /* *树Id */ private Long treeId;}决策树TreeRich
public class TreeRich { /* * 决策树的根节点
*/
private TreeRoot treeRoot;
/*
* 决策树的子节点
*/
private Map<Long, TreeNode> treeNodeMap;
}至此我们的核心对象设计完毕,下面可以来设计接口将他们编排起来
image.png
后续新增规则,可以通过实现这个接口,比如UserAgeFilter针对用户年龄做一些处理,UserGenerFilter针对用户性别做一些处理
public interface LogicFilter { /** * 逻辑决策器
*
* @param matterValue 决策值
* @param treeNodeLineInfoList 决策节点
* @return 下一个节点Id
*/
Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList); /** * 获取决策值
*
* @param decisionMatter 决策物料
* @return 决策值
*/
String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);}public abstract class BaseLogic implements LogicFilter{ @Override public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) { for (TreeNodeLink nodeLine : treeNodeLinkList) { if (decisionLogic(matterValue, nodeLine)){ return nodeLine.getNodeIdTo(); } } return 0L; } @Override public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter); 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; } }}站点过滤器SourceTypeFilter
/* *站点类型过滤器 */public class SourceTypeFilter extends BaseLogic{ @Override public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) { return decisionMatter.get(RuleType.USER_SOURCE.getType()); }}用户类型过滤器
/* *用户类型过滤器 */public class UserTypeFilter extends BaseLogic{ @Override public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) { return decisionMatter.get(RuleType.USER_LEVEL.getType()); }}目前两个决策逻辑的节点获取值的方式都非常简单,只是获取用户的入参即可。实际的业务开发可以从数据库、RPC接口、缓存运算等各种方式获取。
IEngine
对于使用方来说也同样需要定义统一的接口操作,这样的好处非常方便后续拓展出不同类型的决策引擎,也就是可以建造不同的决策工厂。
public interface IEngine { /* * @param treeId 决策树id
* @param userId 用户id
* @param treeRich 整棵决策树
* @param decisionMatter 决策值
* @return EngineResult
*/
EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);
}EngineConfig
public class EngineConfig { static Map<String, LogicFilter> logicFilterMap; static { logicFilterMap = new ConcurrentHashMap<>(); logicFilterMap.put(RuleType.USER_SOURCE.getType(), new SourceTypeFilter()); logicFilterMap.put(RuleType.USER_LEVEL.getType(), new UserTypeFilter()); }}EngineBase责任链的模式
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, Long treeId, String userId, Map<String, String> decisionMatter) { TreeRoot treeRoot = treeRich.getTreeRoot(); Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap(); // 规则树根ID Long rootNodeId = treeRoot.getTreeRootNodeId(); TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId); //节点类型[NodeType];1子叶、2果实 while (treeNodeInfo.getNodeType().equals(1)) { String ruleKey = treeNodeInfo.getRuleKey(); LogicFilter logicFilter = logicFilterMap.get(ruleKey); String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter); Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList()); treeNodeInfo = treeNodeMap.get(nextNode); String log = "决策树引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}"; String.format(log, treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue); System.out.println(log); } return treeNodeInfo; }}TreeEngineHandle
public class TreeEngineHandle extends EngineBase{ @Override public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) { // 决策流程 TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter); // 决策结果 return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue()); }}NodeType
public enum NodeType { ROOT(0,"根节点"), LEAF(1,"叶子节点"), RESULT(2,"结果节点") ; private Integer code; private String desc;}RuleLimitType
public enum RuleLimitType { EQUAL(1,"相等"), GREATER(2,"大于"), LESS(3,"小于"), GREATER_OR_EQUAL(4,"大于等于"), LESS_OR_EQUAL(5,"小于等于"), ; private Integer code; private String desc;}RuleType
public enum RuleType { USER_AGE(1,"userAge","用户年龄"), USER_SEX(2,"userSex","用户性别"), USER_LEVEL(3,"userLevel","用户级别"), USER_SOURCE(4,"userSource","用户来源"), ; private Integer code; private String type; private String desc;}RuleConstant
public class RuleConstant { //用户来源于A站点 public static final String USER_SOURCE_A = "A"; //用户来源于B站点 public static final String USER_SOURCE_B = "B"; //用户来源于C站点 public static final String USER_SOURCE_C = "C"; //用户超级VIP public static final String USER_LEVEL_SUPER_VIP = "SUPER_VIP"; //用户VIP public static final String USER_LEVEL_VIP = "VIP"; //普通用户 public static final String USER_LEVEL_COMMON = "COMMON"; //金牌会员 public static final String USER_LEVEL_GOLD = "GOLD"; //银牌会员 public static final String USER_LEVEL_SILVER = "SILVER"; //铜牌会员 public static final String USER_LEVEL_BRONZE = "BRONZE"; //皇冠会员 public static final String USER_LEVEL_CROWN = "CROWN";}TestMain
package 营销;import cn.hutool.json.JSONUtil;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class TestMain { static TreeRich treeRich; public static void main(String[] args) { init(); System.out.println("决策树组合结构信息:"); System.out.println(JSONUtil.toJsonStr(treeRich)); IEngine treeEngineHandle = new TreeEngineHandle(); Map<String, String> decisionMatter = new HashMap<>(); //用户是来源于A站点的普通VIP时 获取对应的优惠 decisionMatter.put(RuleType.USER_SOURCE.getType(), "A"); decisionMatter.put(RuleType.USER_LEVEL.getType(), RuleConstant.USER_LEVEL_VIP); EngineResult result = treeEngineHandle.process(10001L, "12122333", treeRich, decisionMatter); System.out.println("测试结果:"); System.out.println(JSONUtil.toJsonStr(result)); } //初始化规则 //某电商公司是多站点结构,目前已经开设了ABC三个子站点,这3个子站点的会员等级体系不同, //但产品模型和数据是完全一致的,产品平时在各个站点分别有不同的销售价格;现计划在全公司范围内进行618大促, //活动期间为6月17日零时-6月19日零时,针对不同用户的会员等级,对产品销售实行不同折扣优惠; //请设计相关的系统对外提供商品实时价格获取功能; //A站点 // 超级VIP用户:7折优惠 // VIP用户:9折优惠 // 普通用户:无优惠 //B站点 // 金牌客户:6.5折优惠 // 银牌客户:7.5折优惠 // 铜牌客户:8.5折优惠 // 普通用户:无优惠 //C站点 // 皇冠会员:8折优惠 // 普通用户:无优惠 public static 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(RuleType.USER_SOURCE.getType()); treeNode_01.setRuleDesc(RuleType.USER_SOURCE.getDesc()); // 链接:1->11 即根节点 -> A站点 TreeNodeLink treeNodeLink_11 = new TreeNodeLink(); treeNodeLink_11.setNodeIdFrom(1L); treeNodeLink_11.setNodeIdTo(11L); treeNodeLink_11.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_11.setRuleLimitValue("A"); // 链接:1->12 即根节点 -> B站点 TreeNodeLink treeNodeLink_12 = new TreeNodeLink(); treeNodeLink_12.setNodeIdFrom(1L); treeNodeLink_12.setNodeIdTo(12L); treeNodeLink_12.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_12.setRuleLimitValue("B"); // 链接:1->13 即根节点 -> C站点 TreeNodeLink treeNodeLink_13 = new TreeNodeLink(); treeNodeLink_13.setNodeIdFrom(1L); treeNodeLink_13.setNodeIdTo(13L); treeNodeLink_13.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_13.setRuleLimitValue("C"); List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>(); treeNodeLinkList_1.add(treeNodeLink_11); treeNodeLinkList_1.add(treeNodeLink_12); treeNodeLinkList_1.add(treeNodeLink_13); treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1); // 节点:11 即A站点 TreeNode treeNode_11 = new TreeNode(); treeNode_11.setTreeId(10001L); treeNode_11.setTreeNodeId(11L); treeNode_11.setNodeType(NodeType.LEAF.getCode()); treeNode_11.setNodeValue(null); treeNode_11.setRuleKey(RuleType.USER_LEVEL.getType()); treeNode_11.setRuleDesc(RuleType.USER_LEVEL.getDesc()); // 链接:11->111 A站点的超级VIP用户 TreeNodeLink treeNodeLink_111 = new TreeNodeLink(); treeNodeLink_111.setNodeIdFrom(11L); treeNodeLink_111.setNodeIdTo(111L); treeNodeLink_111.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_111.setRuleLimitValue(RuleConstant.USER_LEVEL_SUPER_VIP); // 链接:11->112 A站点的VIP用户 TreeNodeLink treeNodeLink_112 = new TreeNodeLink(); treeNodeLink_112.setNodeIdFrom(11L); treeNodeLink_112.setNodeIdTo(112L); treeNodeLink_112.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_112.setRuleLimitValue(RuleConstant.USER_LEVEL_VIP); // 链接:11->113 A站点的普通用户 TreeNodeLink treeNodeLink_113 = new TreeNodeLink(); treeNodeLink_113.setNodeIdFrom(11L); treeNodeLink_113.setNodeIdTo(112L); treeNodeLink_113.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_113.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON); List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>(); treeNodeLinkList_11.add(treeNodeLink_111); treeNodeLinkList_11.add(treeNodeLink_112); treeNodeLinkList_11.add(treeNodeLink_113); treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11); // 节点:12 B站点的会员 TreeNode treeNode_12 = new TreeNode(); treeNode_12.setTreeId(10001L); treeNode_12.setTreeNodeId(12L); treeNode_12.setNodeType(NodeType.LEAF.getCode()); treeNode_12.setNodeValue(null); treeNode_12.setRuleKey(RuleType.USER_LEVEL.getType()); treeNode_12.setRuleDesc(RuleType.USER_LEVEL.getDesc()); // 链接:12->121 B站点金牌会员 TreeNodeLink treeNodeLink_121 = new TreeNodeLink(); treeNodeLink_121.setNodeIdFrom(12L); treeNodeLink_121.setNodeIdTo(121L); treeNodeLink_121.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_121.setRuleLimitValue(RuleConstant.USER_LEVEL_GOLD); // 链接:12->122 B站点银牌会员 TreeNodeLink treeNodeLink_122 = new TreeNodeLink(); treeNodeLink_122.setNodeIdFrom(12L); treeNodeLink_122.setNodeIdTo(122L); treeNodeLink_122.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_122.setRuleLimitValue(RuleConstant.USER_LEVEL_SILVER); // 链接:12->123 B站点铜牌会员 TreeNodeLink treeNodeLink_123 = new TreeNodeLink(); treeNodeLink_123.setNodeIdFrom(12L); treeNodeLink_123.setNodeIdTo(123L); treeNodeLink_123.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_123.setRuleLimitValue(RuleConstant.USER_LEVEL_BRONZE); // 链接:12->123 B站点普通用户 TreeNodeLink treeNodeLink_124 = new TreeNodeLink(); treeNodeLink_124.setNodeIdFrom(12L); treeNodeLink_124.setNodeIdTo(124L); treeNodeLink_124.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_124.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON); List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>(); treeNodeLinkList_12.add(treeNodeLink_121); treeNodeLinkList_12.add(treeNodeLink_122); treeNodeLinkList_12.add(treeNodeLink_123); treeNodeLinkList_12.add(treeNodeLink_124); treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12); // 节点:13 C站点的会员 TreeNode treeNode_13 = new TreeNode(); treeNode_13.setTreeId(10001L); treeNode_13.setTreeNodeId(13L); treeNode_13.setNodeType(NodeType.LEAF.getCode()); treeNode_13.setNodeValue(null); treeNode_13.setRuleKey(RuleType.USER_LEVEL.getType()); treeNode_13.setRuleDesc(RuleType.USER_LEVEL.getDesc()); // 链接:13->131 C站点金牌会员 TreeNodeLink treeNodeLink_131 = new TreeNodeLink(); treeNodeLink_131.setNodeIdFrom(13L); treeNodeLink_131.setNodeIdTo(131L); treeNodeLink_131.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_131.setRuleLimitValue(RuleConstant.USER_LEVEL_CROWN); // 链接:13->132 C站点普通用户 TreeNodeLink treeNodeLink_132 = new TreeNodeLink(); treeNodeLink_132.setNodeIdFrom(13L); treeNodeLink_132.setNodeIdTo(132L); treeNodeLink_132.setRuleLimitType(RuleLimitType.EQUAL.getCode()); treeNodeLink_132.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON); List<TreeNodeLink> treeNodeLinkList_13 = new ArrayList<>(); treeNodeLinkList_13.add(treeNodeLink_131); treeNodeLinkList_13.add(treeNodeLink_132); treeNode_13.setTreeNodeLinkList(treeNodeLinkList_13); // 构建结果节点 // 结果节点:111 A站点的超级会员 TreeNode treeNode_111 = new TreeNode(); treeNode_111.setTreeId(10001L); treeNode_111.setTreeNodeId(111L); treeNode_111.setNodeType(NodeType.RESULT.getCode()); treeNode_111.setNodeValue("7折优惠"); // 结果节点:112 A站点的会员 TreeNode treeNode_112 = new TreeNode(); treeNode_112.setTreeId(10001L); treeNode_112.setTreeNodeId(112L); treeNode_112.setNodeType(NodeType.RESULT.getCode()); treeNode_112.setNodeValue("9折优惠"); // 结果节点:113 A站点的普通用户 TreeNode treeNode_113 = new TreeNode(); treeNode_113.setTreeId(10001L); treeNode_113.setTreeNodeId(113L); treeNode_113.setNodeType(NodeType.RESULT.getCode()); treeNode_113.setNodeValue("无优惠"); // 结果节点:121 B站点的金牌会员 TreeNode treeNode_121 = new TreeNode(); treeNode_121.setTreeId(10001L); treeNode_121.setTreeNodeId(121L); treeNode_121.setNodeType(NodeType.RESULT.getCode()); treeNode_121.setNodeValue("6.5折优惠"); // 结果节点:122 B站点的银牌会员牌会员 TreeNode treeNode_122 = new TreeNode(); treeNode_122.setTreeId(10001L); treeNode_122.setTreeNodeId(122L); treeNode_122.setNodeType(NodeType.RESULT.getCode()); treeNode_122.setNodeValue("7.5折优惠"); // 结果节点:123 B站点的铜牌会员牌会员 TreeNode treeNode_123 = new TreeNode(); treeNode_123.setTreeId(10001L); treeNode_123.setTreeNodeId(123L); treeNode_123.setNodeType(NodeType.RESULT.getCode()); treeNode_123.setNodeValue("8.5折优惠"); // 结果节点:124 B站点的普通用户 TreeNode treeNode_124 = new TreeNode(); treeNode_124.setTreeId(10001L); treeNode_124.setTreeNodeId(124L); treeNode_124.setNodeType(NodeType.RESULT.getCode()); treeNode_124.setNodeValue("无优惠"); // C站点的结果节点:131 C站点的黄金会员 TreeNode treeNode_131 = new TreeNode(); treeNode_131.setTreeId(10001L); treeNode_131.setTreeNodeId(131L); treeNode_131.setNodeType(NodeType.RESULT.getCode()); treeNode_131.setNodeValue("8折优惠"); TreeNode treeNode_132 = new TreeNode(); treeNode_132.setTreeId(10001L); treeNode_132.setTreeNodeId(132L); treeNode_132.setNodeType(NodeType.RESULT.getCode()); treeNode_132.setNodeValue("无优惠"); // 树根 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(13L, treeNode_13); treeNodeMap.put(111L, treeNode_111); treeNodeMap.put(112L, treeNode_112); treeNodeMap.put(121L, treeNode_121); treeNodeMap.put(122L, treeNode_122); treeNodeMap.put(123L, treeNode_123); treeNodeMap.put(124L, treeNode_124); treeNodeMap.put(131L, treeNode_131); treeNodeMap.put(132L, treeNode_132); treeRich = new TreeRich(treeRoot, treeNodeMap); }}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。