#yyds干货盘点#-设计模式分享-组合模式

高斋晓开卷,独共圣人语。这篇文章主要讲述#yyds干货盘点#-设计模式分享-组合模式相关的知识,希望能为你提供帮助。
组合模式(Composite Pattern) 概念简介 作用及优势
        - 将复杂的逻辑拆分,使得客户端调用复杂元素的时候变得简单,就是封装起来给客户端调用
        - 扩展性强,需要新增规则时,只需要动态的增加树节点即可
劣势

  • 每个节点、包括连接节点的树枝,都是具体实现类,没法做到接口层次的抽象,可能会有点违背依赖倒置原则;
  • 在需求不是很复杂的场景下,逻辑相对来说会过于复杂,节点也多,维护类的成本也上来了;
场景
        - 文件夹目录
        - 部门的层级关系
        - 树形菜单
       
这里举个例子:
  • 温度> 30°,有太阳 ==》 穿短袖、五分裤
  • 温度> 30°,无太阳 ==》 穿短袖、九分裤
  • 温度< 30°,有太阳 ==》 穿长袖、五分裤
  • 温度< 30°,无太阳 ==》 穿长袖、九分裤
看上面我们可以这么做,将每个条件抽离出来(温度、太阳)作为一个个节点,然后再将 > 、< 、=、有、无这一类作为对比规则保存起来,用一个switch+枚举保存;然后开始组合 温度节点 → 规则与界限(> , 30°)→ 太阳节点 → 规则与界限(=, 有太阳)→ 果实节点(这里就是具体返回 == 穿短袖、五分裤);这样就可以组合出很多场景来了,这样虽然看起来没有if else来的快,可是之后需要加 温度界限为 10°、 20°等情况的时候,有需要加很多if else,这样维护起来就相当麻烦,而组合模式,这需要加上一个条件节点、一个规则节点和果实节点,也不会对之前的代码有很大的侵入性;
代码 案例描述
详细逻辑看图:
#yyds干货盘点#-设计模式分享-组合模式

文章图片

工程目录
#yyds干货盘点#-设计模式分享-组合模式

文章图片

项目类图
【#yyds干货盘点#-设计模式分享-组合模式】
#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"


    推荐阅读