一、组合模式介绍

1. 解决的问题

在树形构造的问题中,含糊了简略元素和简单元素的概念,客户端能够像解决简略元素一样来解决简单元素,从而使客户端和简单元素的外部元素解耦。

2. 定义

组合元素是一种结构型设计模式,能够应用它将对象组合成树状构造,并能像应用独立对象一样应用它们。

3. 利用场景

  • 须要实现树状对象构造,能够应用组合模式。
  • 心愿客户端代码以雷同形式解决简略和简单元素,能够应用组合模式。

二、组合模式优缺点

1. 长处

  • 能够利用多态和递归机制更不便地应用简单树结构。
  • 开闭准则:无需更改现有代码,就能够在利用中增加新元素,使其成为对象树的一部分。

2. 毛病

  • 对于性能差别较大的类,提供公共接口会有艰难。在特定状况下,须要过渡一般化组件接口,使其变得令人难以了解。

三、组合模式利用实例:决策树实现精准化经营

1. 实例场景

精准化经营在咱们的业务开发中越来越重要,产品往往会依据性别、年龄、查看偏好等条件来决定给不同范畴的用户推送不同的音讯,目前就以该场景来实现组合场景。

2. 组合模式实现

本次组合模式的实现思维是依靠决策树的模式。

根底概念

决策树记录了以后根节点和全副节点列表。

非叶子节点蕴含策略名、节点策略列表。

节点策略蕴含下一个字节点、以后决策类型(大于、等于等条件)、以后决策类型。

叶子节点定义了最终的策略,如推送二次元文章、推送财经类文章等。

决策树遍历步骤

  1. 当发现节点为非叶子节点时,依据策略名获取以后策略。
  2. 依据策略数据遍历节点策略列表,判断满足哪种节点策略。
  3. 依据满足的节点策略获取下一个节点。
  4. 当发现节点为叶子节点,返回节点数据,遍历完结。
2.1 工程构造
composite-pattern└─ src    ├─ main    │    └─ java    │    └─ org.design.pattern.composite    │       ├─ model    │       │  ├─ tree    │       │  │  ├─ Tree.java    │       │  │  ├─ TreeNode.java    │       │  │  └─ TreeNodeDecision.java    │       │  └─ decision    │       │     ├─ DecisionResult.java    │       │     ├─ LogicDecision.java    │       │     ├─ LogicBaseDecision.java    │       │     └─ impl    │       │        ├─ UserGenderDecision.java    │       │        └─ UserAgeDecision.java    │       └─ service    │          ├─ DecisionTreeService.java    │          ├─ DecisionTreeBaseService.java    │          └─ impl    │              └─ UserPushDecisionTreeService.java    └─ test        └─ java            └─ org.design.pattern.composite.test                  └─ TreeTest.java
2.2 代码实现
2.2.1 树相干模型

决策树

/** * 决策树 */@Getter@Setterpublic class Tree {    /**     * id     */    private Long id;    /**     * 名称     */    private String name;    /**     * 根节点     */    private TreeNode treeRootNode;    /**     * 节点列表     */    private Map<Long, TreeNode> treeNodeMap;}

决策树节点

/** * 决策树节点 */@Setter@Getterpublic class TreeNode {    /**     * 节点id     */    private Long nodeId;    /**     * 节点类型     */    private String NodeType;    /**     * 节点值     */    private Object NodeValue;    /**     * 起源节点id     */    private Long fromNodeId;    /**     * 去向节点id     */    private Long toNodeId;    /**     * 决策名     */    private String decisionName;    /**     * 决策规定列表     */    private List<TreeNodeDecision> treeNodeDecisionList;}

决策树节点决策

/** * 决策树节点决策 */@Getter@Setterpublic class TreeNodeDecision {    /**     * 决策名称     */    private String name;    /**     * 起源节点     */    private Long fromNode;    /**     * 去向节点     */    private Long toNode;    /**     * 决策类型     */    private String decisionType;    /**     * 决策值     */    private String decisionValue;}
2.2.2 决策相干模型

逻辑决策器接口

/** * 逻辑决策器接口 */public interface LogicDecision {    /**     * 获取决策值     * @param treeId 决策树id     * @param userId 用户id     * @param userData 用户数据     * @return String     */    String getDecisionValue(Long treeId, String userId, Map<String, String> userData);    /**     * 过滤出节点决策     * @param decisionValue 决策值     * @param treeNodeDecisionList 节点决策列表     * @return     */    Long filterDecisionNode(String decisionValue, List<TreeNodeDecision> treeNodeDecisionList);}

逻辑根底决策器

/** * 逻辑根底决策器 */public abstract class LogicBaseDecision implements LogicDecision {    @Override    public abstract String getDecisionValue(Long treeId, String userId, Map<String, String> userData);;    @Override    public Long filterDecisionNode(String decisionValue, List<TreeNodeDecision> treeNodeDecisionList) {        for (TreeNodeDecision treeNodeDecision : treeNodeDecisionList) {            if (filterDecisionByType(decisionValue, treeNodeDecision)) {                return treeNodeDecision.getToNode();            }        }        return 0L;    }    /**     * 依据类型过滤决策     * @param decisionValue 决策值     * @param treeNodeDecision 节点决策     * @return boolean     */    private boolean filterDecisionByType(String decisionValue, TreeNodeDecision treeNodeDecision) {        switch (treeNodeDecision.getDecisionType()) {            case "eq":                return decisionValue.equals(treeNodeDecision.getDecisionValue());            case "gt":                return Double.parseDouble(decisionValue) > Double.parseDouble(treeNodeDecision.getDecisionValue());            case "gte":                return Double.parseDouble(decisionValue) >= Double.parseDouble(treeNodeDecision.getDecisionValue());            case "lt":                return Double.parseDouble(decisionValue) < Double.parseDouble(treeNodeDecision.getDecisionValue());            case "lte":                return Double.parseDouble(decisionValue) <= Double.parseDouble(treeNodeDecision.getDecisionValue());            default:                return false;        }    }}

用户性别决策器

/** * 用户性别决策器 */public class UserGenderDecision extends LogicBaseDecision {    @Override    public String getDecisionValue(Long treeId, String userId, Map<String, String> userData) {        return userData.get("gender");    }}

用户年龄决策器

/** * 用户年龄决策器 */public class UserAgeDecision extends LogicBaseDecision {    @Override    public String getDecisionValue(Long treeId, String userId, Map<String, String> userData) {        return userData.get("age");    }}

决策后果

/** * 决策后果 */@Getter@Setter@AllArgsConstructorpublic class DecisionResult {    /**     * 用户id     */    private String userId;    /**     * 决策树id     */    private Long treeId;    /**     * 后果节点值     */    private Object resultNodeValue;}
2.2.3 决策树服务

决策树服务接口

/** * 决策树服务接口 */public interface DecisionTreeService {    DecisionResult process(final Tree treeId, final String userId, final Map<String, String> userData);}

决策树根底服务

/** * 决策树根底服务 */public abstract class DecisionTreeBaseService implements DecisionTreeService {    private final Logger log = LoggerFactory.getLogger(DecisionTreeBaseService.class);    /**     * 决策map     */    protected Map<String, LogicDecision> logicDecisionMap = new ConcurrentHashMap<>();    @Override    public abstract DecisionResult process(Tree tree, String userId, Map<String, String> userData);    /**     * 寻找叶子节点     * @param tree 决策树     * @param userId 用户id     * @param userData 用户数据     * @return     */    protected TreeNode findLeafNode(Tree tree, String userId, Map<String, String> userData) {        TreeNode treeNode = tree.getTreeRootNode();        while (treeNode.getNodeType().equals("branch")) {            //获取节点决策            LogicDecision logicDecision = logicDecisionMap.get(treeNode.getDecisionName());            //获取须要决策的值            String decisionValue = logicDecision.getDecisionValue(tree.getId(), userId, userData);            //依据节点决策列表,获取下一节点id            Long nextNodeId = logicDecision.filterDecisionNode(decisionValue, treeNode.getTreeNodeDecisionList());            log.info(                    "决策树:{},用户id:{},以后节点id:{},下一节点id:{},决策名:{},决策值:{}",                    tree.getId(),                    userId,                    treeNode.getNodeId(),                    nextNodeId,                    treeNode.getDecisionName(),                    decisionValue            );            treeNode = tree.getTreeNodeMap().get(nextNodeId);        }        return treeNode;    }}

用户推送决策树服务

/** * 用户推送决策树服务 */public class UserPushDecisionTreeService extends DecisionTreeBaseService {    public UserPushDecisionTreeService() {        this.logicDecisionMap.put("gender", new UserGenderDecision());        this.logicDecisionMap.put("age", new UserAgeDecision());    }    @Override    public DecisionResult process(Tree tree, String userId, Map<String, String> userData) {        //寻找最终决策节点        TreeNode resultNode = findLeafNode(tree, userId, userData);        //决策后果        return new DecisionResult(userId, tree.getId(), resultNode.getNodeValue());    }}
2.3 测试验证
2.3.1 测试验证类
/** * 决策树测试类 */public class TreeTest {    private final Logger log = LoggerFactory.getLogger(TreeTest.class);    private Tree tree;    @Before    public void initTree() {        //根节点(用户性别)        TreeNode rootNode = new TreeNode();        rootNode.setNodeId(1L);        rootNode.setNodeType("branch");        //根节点的子节点        //子节点1        TreeNode rootChildOne = new TreeNode();        rootChildOne.setNodeId(11L);        rootChildOne.setNodeType("branch");        rootChildOne.setFromNodeId(rootNode.getNodeId());        //子节点2        TreeNode rootChildTwo = new TreeNode();        rootChildTwo.setNodeId(12L);        rootChildTwo.setNodeType("branch");        rootChildTwo.setFromNodeId(rootNode.getNodeId());        //孙节点        //孙节点1        TreeNode grandChildrenOne = new TreeNode();        grandChildrenOne.setNodeId(111L);        grandChildrenOne.setNodeType("leaf");        grandChildrenOne.setNodeValue("推送二次元类文章");        grandChildrenOne.setFromNodeId(rootChildOne.getFromNodeId());        //孙节点2        TreeNode grandChildrenTwo = new TreeNode();        grandChildrenTwo.setNodeId(112L);        grandChildrenTwo.setNodeType("leaf");        grandChildrenTwo.setNodeValue("推送财经类文章");        grandChildrenTwo.setFromNodeId(rootChildOne.getFromNodeId());        //孙节点3        TreeNode grandChildrenThree = new TreeNode();        grandChildrenThree.setNodeId(121L);        grandChildrenThree.setNodeType("leaf");        grandChildrenThree.setNodeValue("推送A类文章");        grandChildrenThree.setFromNodeId(rootChildTwo.getFromNodeId());        //孙节点4        TreeNode grandChildrenFour = new TreeNode();        grandChildrenFour.setNodeId(122L);        grandChildrenFour.setNodeType("leaf");        grandChildrenFour.setNodeValue("推送B类文章");        grandChildrenFour.setFromNodeId(rootChildOne.getFromNodeId());        //根节点决策        String rootDecisionName = "gender";        rootNode.setDecisionName(rootDecisionName);        //性别男        TreeNodeDecision manDecision = new TreeNodeDecision();        manDecision.setDecisionType("eq");        manDecision.setDecisionValue("man");        manDecision.setFromNode(rootNode.getNodeId());        manDecision.setToNode(rootChildOne.getNodeId());        //性别女        TreeNodeDecision womanDecision = new TreeNodeDecision();        womanDecision.setDecisionType("eq");        womanDecision.setDecisionValue("woman");        womanDecision.setFromNode(rootNode.getNodeId());        womanDecision.setToNode(rootChildTwo.getNodeId());        //设置根节点决策        List<TreeNodeDecision> rootNodeDecisionList = new ArrayList<>(2);        rootNodeDecisionList.add(manDecision);        rootNodeDecisionList.add(womanDecision);        rootNode.setTreeNodeDecisionList(rootNodeDecisionList);        //子节点决策        String childDecisionName = "age";        rootChildOne.setDecisionName(childDecisionName);        rootChildTwo.setDecisionName(childDecisionName);        //子节点1决策        //男,年龄≤24        TreeNodeDecision ageDecisionOne = new TreeNodeDecision();        ageDecisionOne.setDecisionType("lte");        ageDecisionOne.setDecisionValue("24");        ageDecisionOne.setFromNode(rootChildOne.getNodeId());        ageDecisionOne.setToNode(grandChildrenOne.getNodeId());        //男,年龄>24        TreeNodeDecision ageDecisionTwo = new TreeNodeDecision();        ageDecisionTwo.setDecisionType("gt");        ageDecisionTwo.setDecisionValue("24");        ageDecisionTwo.setFromNode(rootChildOne.getNodeId());        ageDecisionTwo.setToNode(grandChildrenTwo.getNodeId());        //设置子节点1决策        List<TreeNodeDecision> rootChildOneDecisionList = new ArrayList<>(2);        rootChildOneDecisionList.add(ageDecisionOne);        rootChildOneDecisionList.add(ageDecisionTwo);        rootChildOne.setTreeNodeDecisionList(rootChildOneDecisionList);        //子节点2决策        //女,年龄≤35        TreeNodeDecision ageDecisionThree = new TreeNodeDecision();        ageDecisionThree.setDecisionType("lte");        ageDecisionThree.setDecisionValue("35");        ageDecisionThree.setFromNode(rootChildTwo.getNodeId());        ageDecisionThree.setToNode(grandChildrenThree.getNodeId());        //女,年龄>35        TreeNodeDecision ageDecisionFour = new TreeNodeDecision();        ageDecisionFour.setDecisionType("gt");        ageDecisionFour.setDecisionValue("35");        ageDecisionFour.setFromNode(rootChildTwo.getNodeId());        ageDecisionFour.setToNode(grandChildrenFour.getNodeId());        //设置子节点2决策        List<TreeNodeDecision> rootChildTwoDecisionList = new ArrayList<>(2);        rootChildTwoDecisionList.add(ageDecisionThree);        rootChildTwoDecisionList.add(ageDecisionFour);        rootChildTwo.setTreeNodeDecisionList(rootChildTwoDecisionList);        //设置决策树        tree = new Tree();        tree.setId(1L);        tree.setName("精准化经营音讯推送决策树");        //决策树根节点        tree.setTreeRootNode(rootNode);        //决策树节点        Map<Long, TreeNode> treeNodeMap = new TreeMap<>();        treeNodeMap.put(rootNode.getNodeId(), rootNode);        treeNodeMap.put(rootChildOne.getNodeId(), rootChildOne);        treeNodeMap.put(rootChildTwo.getNodeId(), rootChildTwo);        treeNodeMap.put(grandChildrenOne.getNodeId(), grandChildrenOne);        treeNodeMap.put(grandChildrenTwo.getNodeId(), grandChildrenTwo);        treeNodeMap.put(grandChildrenThree.getNodeId(), grandChildrenThree);        treeNodeMap.put(grandChildrenFour.getNodeId(), grandChildrenFour);        tree.setTreeNodeMap(treeNodeMap);    }    @Test    public void testUserPushDecisionTree() {        UserPushDecisionTreeService userPushDecisionTreeService = new UserPushDecisionTreeService();        //用户数据        Map<String, String> userData = new HashMap<>();        userData.put("gender", "man");        userData.put("age", "29");        DecisionResult result = userPushDecisionTreeService.process(tree, "yiyufxst", userData);        log.info("性别:{},年龄:{}的用户推送{}", userData.get("gender"), userData.get("age"), result.getResultNodeValue());        userData.put("age", "24");        result = userPushDecisionTreeService.process(tree, "yiyufxst", userData);        log.info("性别:{},年龄:{}的用户推送{}", userData.get("gender"), userData.get("age"), result.getResultNodeValue());        userData.put("gender", "woman");        userData.put("age", "35");        result = userPushDecisionTreeService.process(tree, "马冬梅", userData);        log.info("性别:{},年龄:{}的用户推送{}", userData.get("gender"), userData.get("age"), result.getResultNodeValue());        userData.put("age", "40");        result = userPushDecisionTreeService.process(tree, "马冬梅", userData);        log.info("性别:{},年龄:{}的用户推送{}", userData.get("gender"), userData.get("age"), result.getResultNodeValue());    }}
2.3.2 测试后果
18:19:50.198 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:yiyufxst,以后节点id:1,下一节点id:11,决策名:gender,决策值:man18:19:50.201 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:yiyufxst,以后节点id:11,下一节点id:112,决策名:age,决策值:2918:19:50.201 [main] INFO  o.d.pattern.composite.test.TreeTest - 性别:man,年龄:29的用户推送推送财经类文章18:19:50.201 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:yiyufxst,以后节点id:1,下一节点id:11,决策名:gender,决策值:man18:19:50.201 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:yiyufxst,以后节点id:11,下一节点id:111,决策名:age,决策值:2418:19:50.201 [main] INFO  o.d.pattern.composite.test.TreeTest - 性别:man,年龄:24的用户推送推送二次元类文章18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:马冬梅,以后节点id:1,下一节点id:12,决策名:gender,决策值:woman18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:马冬梅,以后节点id:12,下一节点id:121,决策名:age,决策值:3518:19:50.202 [main] INFO  o.d.pattern.composite.test.TreeTest - 性别:woman,年龄:35的用户推送推送A类文章18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:马冬梅,以后节点id:1,下一节点id:12,决策名:gender,决策值:woman18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户id:马冬梅,以后节点id:12,下一节点id:122,决策名:age,决策值:4018:19:50.202 [main] INFO  o.d.pattern.composite.test.TreeTest - 性别:woman,年龄:40的用户推送推送B类文章Process finished with exit code 0

四、组合模式构造

  1. 组件(Component)接口形容了树中简略我的项目和简单我的项目所共有的操作。
  2. 叶节点(Leaf)是树的根底构造,它不蕴含子项目。

    个别状况下,叶节点最终会实现大部分的理论工作,因为它们无奈把工作委派给其余局部。

  3. 容器(Container)——又名“组合(Composite)”——是蕴含叶节点或其余容器等子项目的单位。容器不晓得其子我的项目所属的具体类,它只通过通用的组件接口与其子项目交互。

    容器接管到申请会将工作调配给本人的子项目,解决两头后果,而后将最终后果返回给客户端。

  4. 客户端(Client)通过组件接口与所有我的项目交互。因而,客户端能以雷同形式与树状构造中的简略或简单我的项目交互。

设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。

源码地址:https://github.com/yiyufxst/design-pattern-java

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深刻设计模式:https://refactoringguru.cn/design-patterns/catalog