高斋晓开卷,独共圣人语。这篇文章主要讲述#yyds干货盘点#-设计模式分享-组合模式相关的知识,希望能为你提供帮助。
组合模式(Composite Pattern)
概念简介
作用及优势
- 将复杂的逻辑拆分,使得客户端调用复杂元素的时候变得简单,就是封装起来给客户端调用
- 扩展性强,需要新增规则时,只需要动态的增加树节点即可
劣势
- 每个节点、包括连接节点的树枝,都是具体实现类,没法做到接口层次的抽象,可能会有点违背依赖倒置原则;
- 在需求不是很复杂的场景下,逻辑相对来说会过于复杂,节点也多,维护类的成本也上来了;
- 文件夹目录
- 部门的层级关系
- 树形菜单
这里举个例子:
- 温度>
30°,有太阳 ==》 穿短袖、五分裤
- 温度>
30°,无太阳 ==》 穿短袖、九分裤
- 温度<
30°,有太阳 ==》 穿长袖、五分裤
- 温度< 30°,无太阳 ==》 穿长袖、九分裤
代码 案例描述
详细逻辑看图:
文章图片
工程目录
文章图片
项目类图
【#yyds干货盘点#-设计模式分享-组合模式】
文章图片
具体实现
note :很多注释写在代码
规则节点(TreeNode): 也即是树节点;
/**
* 功能描述: 决策树节点
*
* @author: WuChengXing
* @create: 2021-06-26 11:43
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TreeNode
/**
* 树id
*/
private Long treeId;
/**
* 树节点id
*/
private Long treeNodeId;
/**
* 节点类型:1=叶子节点,2=果实节点
*/
private Integer treeNodeType;
private Object treeNodeValue;
/**
* 这里是节点规则:对应是按什么去过滤数据,比如(性别(gender)、年龄(age))
*/
private String ruleKey;
private String ruleDesc;
/**
* 这里是维持 两个节点之前的关系的“树枝”,可能有多种情况
*/
private List<
TreeNodeLink>
treeNodeLinks;
树枝节点(TreeNodeLink):
/**
* 功能描述: 节点之间的链路指向
*
* @author: WuChengXing
* @create: 2021-06-26 11:45
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TreeNodeLink
/**
* 上一个节点的id
*/
private Long nodeIdFrom;
/**
* 下一个节点id
*/
private Long nodeIdTo;
/**
* 这个是过滤的类型;这个是枚举,查看 == ExpressionEnum
*/
private Integer ruleLimitType;
/**
* 这个是具体的值:比如 man、women、19、52等之类的限定值
*/
private String ruleLimitValue;
这里说下,“树枝”起到了承上启下的作用,关联着两个节点,当“树枝”节点的校验通过了,就会指向下一个节点,直到最后结了果实;
存放节点信息的TreeRich和根节点信息TreeRoot,以及返回值EngineResult:
/**
* 功能描述:
*
* @author: WuChengXing
* @create: 2021-06-26 11:55
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TreeRich private TreeRoot treeRoot;
private Map<
Long, TreeNode>
treeNodeMap;
------------------------------------------------/**
* 功能描述: 决策树根
*
* @author: WuChengXing
* @create: 2021-06-26 11:48
**/
@Data
public class TreeRoot
/**
* 该决策书的id
*/
private Long treeId;
/**
* 该决策树的根节点,也是第一个规则节点的id
*/
private Long treeRootNodeId;
/**
* 树的名称
*/
private String treeName;
private TreeRoot treeRoot;
-------------------------------------------------/**
* 功能描述: 返回值
*
* @author: WuChengXing
* @create: 2021-06-26 11:59
**/
@Data
@Builder
public class EngineResult
private String userId;
private Long treeId;
private Long treeNodeId;
private String treeNodeValue;
决策树过滤器(LogicFilter):
/**
* 功能描述: 决策树过滤器
*
* @author: WuChengXing
* @create: 2021-06-26 12:15
**/
public interface LogicFilter /**
* 逻辑过滤器
*
* @param matterValue决策值
* @param treeNodeLinks 决策节点
* @return
*/
Long filter(String matterValue, List<
TreeNodeLink>
treeNodeLinks);
/**
* 获取决策值方法: 及获取map中的值 age ->
25
*
* @param treeId
* @param userId
* @param decisionMatter 决策物料
* @return
*/
String matterValue(Long treeId, String userId, Map<
String, String>
decisionMatter);
实现类-抽象是实现类,提供相同的方法供调用(BaseLogicFilter):
/**
* 功能描述: 基础决策过滤器,提供一些基础服务
*
* @author: WuChengXing
* @create: 2021-06-26 12:19
**/
public abstract class BaseLogicFilter implements LogicFilter /**
* 过滤到下一个节点(即第一个规则通过了,就指向下一个规则)
* @param matterValue决策值
* @param treeNodeLinks 决策节点
* @return
*/
@Override
public Long filter(String matterValue, List<
TreeNodeLink>
treeNodeLinks)
for (TreeNodeLink treeNodeLink : treeNodeLinks)
// 匹配上了这些规则,继续往下走(这里是“树枝”,里面有界限值和指向)
if (decisionLogic(matterValue, treeNodeLink))
// “树枝”上面的校验通过了,则返回下一个规则节点的id
return treeNodeLink.getNodeIdTo();
return 0L;
public abstract String matterValue(Long treeId, String userId, Map<
String, String>
decisionMatter);
/**
* 校验传入的表达式 =、>
、<
等
* @param matterValue
* @param treeNodeLink
* @return
*/
public Boolean decisionLogic(String matterValue, TreeNodeLink treeNodeLink)
switch (Objects.requireNonNull(ExpressionEnum.getByIndex(treeNodeLink.getRuleLimitType())))
case EQUAL:
return matterValue.equals(treeNodeLink.getRuleLimitValue());
case GRANT:
return Double.parseDouble(matterValue) >
Double.parseDouble(treeNodeLink.getRuleLimitValue());
case LESS:
return Double.parseDouble(matterValue) <
Double.parseDouble(treeNodeLink.getRuleLimitValue());
case LESS_EQUAL:
return Double.parseDouble(matterValue) <
= Double.parseDouble(treeNodeLink.getRuleLimitValue());
case GRANT_EQUAL:
return Double.parseDouble(matterValue) >
= Double.parseDouble(treeNodeLink.getRuleLimitValue());
default:
return false;
这里存在过滤规则,也就是找到对应的节点关系,filter() 方法可以找到下一个节点的id;
枚举和常量值 :
/**
* 功能描述: 表达式枚举
*
* @author: WuChengXing
* @create: 2021-06-26 13:27
**/
@Getter
public enum ExpressionEnum EQUAL(1, "等于"),
GRANT(2, "大于"),
LESS(3, "小于"),
LESS_EQUAL(4, "小于等于"),
GRANT_EQUAL(5, "大于等于"),
;
ExpressionEnum(Integer index, String desc)
this.index = index;
this.desc = desc;
private final Integer index;
private final String desc;
public static ExpressionEnum getByIndex(Integer index)
for (ExpressionEnum expressionEnum : values())
if (index.equals(expressionEnum.getIndex()))
return expressionEnum;
return null;
----------------------------------------------- /**
* 功能描述: 常量
*
* @author: WuChengXing
* @create: 2021-06-26 13:18
**/
public final class Constant
/**
* 叶子节点
*/
public static final Integer NODE_TYPE_LEAF = 1;
/**
* 果实节点
*/
public static final Integer NODE_TYPE_FRUIT = 2;
抽象过滤的具体实现 :UserAgeFilter、UserGenderFilter
/**
* 功能描述: 用户年龄逻辑判断节点
*
* @author: WuChengXing
* @create: 2021-06-26 12:28
**/
public class UserAgeFilter extends BaseLogicFilter @Override
public String matterValue(Long treeId, String userId, Map<
String, String>
decisionMatter)
return decisionMatter.get("age");
--------------------------------------/**
* 功能描述: 性别逻辑判断节点
*
* @author: WuChengXing
* @create: 2021-06-26 12:29
**/
public class UserGenderFilter extends BaseLogicFilter @Override
public String matterValue(Long treeId, String userId, Map<
String, String>
decisionMatter)
return decisionMatter.get("gender");
决策引擎(IEngine):
/**
* 功能描述: 决策引擎
*
* @author: WuChengXing
* @create: 2021-06-26 12:31
**/
public interface IEngine /**
* 处理决策树,返回特定值
* @param treeId
* @param userId
* @param treeRich
* @param decisionMatter
* @return
*/
EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<
String, String>
decisionMatter);
决策引擎的基础实现(BaseEngine):
/**
* 功能描述: 基础执行引擎
*
* @author: WuChengXing
* @create: 2021-06-26 12:51
**/
@Slf4j
public abstract class BaseEngine extends EngineConfig implements IEngine public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<
String, String>
decisionMatter);
public TreeNode engineDecisionMaker(Long treeId, String userId, TreeRich treeRich, Map<
String, String>
decisionMatter)
TreeRoot treeRoot = treeRich.getTreeRoot();
Map<
Long, TreeNode>
treeNodeMap = treeRich.getTreeNodeMap();
// 规则树根Id
Long treeRootNodeId = treeRoot.getTreeRootNodeId();
TreeNode treeNode = treeNodeMap.get(treeRootNodeId);
// treeNodeType: 1=叶子节点,2=果实
while (treeNode.getTreeNodeType() == 1)
// 拿到对应的规则节点对应的key
String ruleKey = treeNode.getRuleKey();
// 拿到过滤规则
LogicFilter logicFilter = logicFilterMap.get(ruleKey);
// 拿到map对应的需要进行比较的值,即客户端传过来的值
String matterValue = https://www.songbingjia.com/android/logicFilter.matterValue(treeId, userId, decisionMatter);
// 这里去通过“树枝”,过了校验就获取下一个规则
Long nextNodeId = logicFilter.filter(matterValue, treeNode.getTreeNodeLinks());
treeNode = treeNodeMap.get(nextNodeId);
log.info("决策树引擎:===>
返回值:ruleKey: , matterValue: , nextNodeId: ", ruleKey, matterValue, nextNodeId);
return treeNode;
决策引擎的实现(EngineHandler):
/**
* 功能描述: 决策引擎的实现
*
* @author: WuChengXing
* @create: 2021-06-26 13:09
**/
public class EngineHandler extends BaseEngine @Override
public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<
String, String>
decisionMatter)
// 决策流程
TreeNode treeNode = engineDecisionMaker(treeId, userId, treeRich, decisionMatter);
return EngineResult.builder().userId(userId).treeId(treeId).treeNodeId(treeNode.getTreeNodeId()).treeNodeValue((String) treeNode.getTreeNodeValue()).build();
过滤引擎(EngineConfig )
/**
* 功能描述: 决策引擎配置
*
* @author: WuChengXing
* @create: 2021-06-26 12:34
**/
@Data
public class EngineConfig
protected static Map<
String, LogicFilter>
logicFilterMap;
/**
* 初始化过滤器
*/
static
logicFilterMap = new HashMap<
>
(4);
logicFilterMap.put("userAge", new UserAgeFilter());
logicFilterMap.put("userGender", new UserGenderFilter());
决策节点初始化(TreeNodeInit):
/**
* 功能描述: 初始化数据
*
* @author: WuChengXing
* @create: 2021-06-26 13:14
**/
public class TreeNodeInit
public TreeRich init()
TreeNode treeNode_01 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(1L)
.treeNodeType(Constant.NODE_TYPE_LEAF)
.treeNodeValue(null)
.ruleKey("userGender")
.ruleDesc("用户性别[男/女]")
.build();
// 1 -->
11
TreeNodeLink treeNodeLink_11 = TreeNodeLink.builder()
.nodeIdFrom(1L)
.nodeIdTo(11L)
.ruleLimitType(ExpressionEnum.EQUAL.getIndex())
.ruleLimitValue("man")
.build();
// 1 -->
12
TreeNodeLink treeNodeLink_12 = TreeNodeLink.builder()
.nodeIdFrom(1L)
.nodeIdTo(12L)
.ruleLimitType(ExpressionEnum.EQUAL.getIndex())
.ruleLimitValue("woman")
.build();
List<
TreeNodeLink>
treeNodeLinks_1 = new ArrayList<
>
();
treeNodeLinks_1.add(treeNodeLink_11);
treeNodeLinks_1.add(treeNodeLink_12);
treeNode_01.setTreeNodeLinks(treeNodeLinks_1);
// ------------------------------------------
TreeNode treeNode_11 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(11L)
.treeNodeType(Constant.NODE_TYPE_LEAF)
.treeNodeValue(null)
.ruleKey("userAge")
.ruleDesc("用户年龄")
.build();
// 11 -->
111
TreeNodeLink treeNodeLink_111 = TreeNodeLink.builder()
.nodeIdFrom(11L)
.nodeIdTo(111L)
.ruleLimitType(ExpressionEnum.LESS.getIndex())
.ruleLimitValue("25")
.build();
// 11 -->
112
TreeNodeLink treeNodeLink_112 = TreeNodeLink.builder()
.nodeIdFrom(11L)
.nodeIdTo(112L)
.ruleLimitType(ExpressionEnum.GRANT_EQUAL.getIndex())
.ruleLimitValue("25")
.build();
List<
TreeNodeLink>
treeNodeLinks_11 = new ArrayList<
>
();
treeNodeLinks_11.add(treeNodeLink_111);
treeNodeLinks_11.add(treeNodeLink_112);
treeNode_11.setTreeNodeLinks(treeNodeLinks_11);
// ------------------------------------------
TreeNode treeNode_12 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(12L)
.treeNodeType(Constant.NODE_TYPE_LEAF)
.treeNodeValue(null)
.ruleKey("userAge")
.ruleDesc("用户年龄")
.build();
// 12 -->
121
TreeNodeLink treeNodeLink_121 = TreeNodeLink.builder()
.nodeIdFrom(12L)
.nodeIdTo(121L)
.ruleLimitType(ExpressionEnum.LESS.getIndex())
.ruleLimitValue("25")
.build();
// 11 -->
122
TreeNodeLink treeNodeLink_122 = TreeNodeLink.builder()
.nodeIdFrom(12L)
.nodeIdTo(122L)
.ruleLimitType(ExpressionEnum.GRANT_EQUAL.getIndex())
.ruleLimitValue("25")
.build();
List<
TreeNodeLink>
treeNodeLinks_12 = new ArrayList<
>
();
treeNodeLinks_12.add(treeNodeLink_121);
treeNodeLinks_12.add(treeNodeLink_122);
treeNode_12.setTreeNodeLinks(treeNodeLinks_12);
/**
* 结果
*/TreeNode treeNode_111 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(111L)
.treeNodeType(Constant.NODE_TYPE_FRUIT)
.treeNodeValue("果实A ===>
男,<
25")
.build();
TreeNode treeNode_112 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(112L)
.treeNodeType(Constant.NODE_TYPE_FRUIT)
.treeNodeValue("果实B ===>
男,>
=25")
.build();
TreeNode treeNode_121 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(121L)
.treeNodeType(Constant.NODE_TYPE_FRUIT)
.treeNodeValue("果实C ===>
女,<
25")
.build();
TreeNode treeNode_122 = TreeNode.builder()
.treeId(10001L)
.treeNodeId(122L)
.treeNodeType(Constant.NODE_TYPE_FRUIT)
.treeNodeValue("果实D ===>
女,>
=25")
.build();
//--------- 树根 ----------------
TreeRoot treeRoot = new TreeRoot();
treeRoot.setTreeId(10001L);
// 这里是记录这整个决策树的第一个规则节点是什么
treeRoot.setTreeRootNodeId(1L);
treeRoot.setTreeName("决策树");
Map<
Long, TreeNode>
treeNodeMap = new HashMap<
>
(16);
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 = new TreeRich();
treeRich.setTreeRoot(treeRoot);
treeRich.setTreeNodeMap(treeNodeMap);
return treeRich;
这个决策节点初始化操作可以放到数据库中的,通过前端UI去操作也是可以的;类似于树节点配置的功能,然后将组合的新树结构存入数据库就行;这样配置起来比较方便;
测试
/**
* 功能描述: 组合模式
*
* @author: WuChengXing
* @create: 2021-06-26 11:41
**/
@Slf4j
public class CombinationModeTest
public static void main(String[] args)
TreeNodeInit treeNodeInit = new TreeNodeInit();
TreeRich init = treeNodeInit.init();
IEngine engine = new EngineHandler();
Map<
String, String>
decisionMap = new HashMap<
>
(2);
// 具体的参数,用于去跟已经配置好了的规则节点去对比
decisionMap.put("gender", "woman");
decisionMap.put("age", "24");
EngineResult result = engine.process(10001L, "sasassa", init, decisionMap);
log.info("result ==>
", JSON.toJSON(result));
结果:
21:36:17.610 [main] INFO com.simple.designpatterns.pattern23.structuretype.combination.service.BaseEngine - 决策树引擎:===>
返回值:ruleKey: userGender, matterValue: woman, nextNodeId: 12
21:36:17.614 [main] INFO com.simple.designpatterns.pattern23.structuretype.combination.service.BaseEngine - 决策树引擎:===>
返回值:ruleKey: userAge, matterValue: 24, nextNodeId: 121
21:36:17.699 [main] INFO com.simple.designpatterns.pattern23.structuretype.combination.CombinationModeTest - result ==>
"treeId":10001,"treeNodeId":121,"treeNodeValue":"果实C ===>
女,<
25","userId":"sasassa"
推荐阅读
- 滑动冲突问题,触摸事件拦截处理
- 重要的ui组件——Behavior
- gitlab 搭建go modules私有仓库
- Django+uwsgi在linux和windows上的部署
- Android 控件架构与自定义控件详解
- Android使用 SO 库时要注意的一些问题
- WordPress自定义程序在自定义程序界面中可完美运行,但在实际设计中却无法运行
- WordPress Customizr主题现代风格-子主题和覆盖模板
- WordPress-定制程序设置会覆盖以前的设置