#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干货盘点#-设计模式分享-组合模式的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# 设计模式之代理模式:动态代理

UML类图 #yyds干货盘点#

#yyds干货盘点# 设计模式之代理模式:静态代理

#yyds干货盘点# js学习笔记四十一单体模式

#yyds干货盘点#Zabbi学习

#yyds干货盘点# js学习笔记四十复杂工厂模式