#yyds干货盘点#-设计模式分享-组合模式
Posted 爱搞技术的吴同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点#-设计模式分享-组合模式相关的知识,希望对你有一定的参考价值。
组合模式(Composite Pattern)
概念
简介
作用及优势
- 将复杂的逻辑拆分,使得客户端调用复杂元素的时候变得简单,就是封装起来给客户端调用
- 扩展性强,需要新增规则时,只需要动态的增加树节点即可
劣势
-
每个节点、包括连接节点的树枝,都是具体实现类,没法做到接口层次的抽象,可能会有点违背依赖倒置原则;
- 在需求不是很复杂的场景下,逻辑相对来说会过于复杂,节点也多,维护类的成本也上来了;
场景
- 文件夹目录
- 部门的层级关系
- 树形菜单
这里举个例子:
-
温度>30°,有太阳 ==》 穿短袖、五分裤
-
温度>30°,无太阳 ==》 穿短袖、九分裤
-
温度<30°,有太阳 ==》 穿长袖、五分裤
- 温度<30°,无太阳 ==》 穿长袖、九分裤
看上面我们可以这么做,将每个条件抽离出来(温度、太阳)作为一个个节点,然后再将 >、<、=、有、无这一类作为对比规则保存起来,用一个switch+枚举保存;然后开始组合 温度节点 → 规则与界限(>, 30°)→ 太阳节点 → 规则与界限(=, 有太阳)→ 果实节点(这里就是具体返回 == 穿短袖、五分裤);这样就可以组合出很多场景来了,这样虽然看起来没有if else来的快,可是之后需要加 温度界限为 10°、 20°等情况的时候,有需要加很多if else,这样维护起来就相当麻烦,而组合模式,这需要加上一个条件节点、一个规则节点和果实节点,也不会对之前的代码有很大的侵入性;
代码
案例描述
详细逻辑看图:
工程目录
项目类图
具体实现
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 = 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"
以上是关于#yyds干货盘点#-设计模式分享-组合模式的主要内容,如果未能解决你的问题,请参考以下文章