乐趣区

关于设计模式:初学-Java-设计模式九实战组合模式-决策树实现精准化运营

一、组合模式介绍

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
@Setter
public class Tree {
    /**
     * id
     */
    private Long id;

    /**
     * 名称
     */
    private String name;

    /**
     * 根节点
     */
    private TreeNode treeRootNode;

    /**
     * 节点列表
     */
    private Map<Long, TreeNode> treeNodeMap;
}

决策树节点

/**
 * 决策树节点
 */
@Setter
@Getter
public 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
@Setter
public 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
@AllArgsConstructor
public 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,决策值:man
18:19:50.201 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户 id:yiyufxst,以后节点 id:11,下一节点 id:112,决策名:age,决策值:29
18: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,决策值:man
18:19:50.201 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户 id:yiyufxst,以后节点 id:11,下一节点 id:111,决策名:age,决策值:24
18: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,决策值:woman
18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户 id:马冬梅,以后节点 id:12,下一节点 id:121,决策名:age,决策值:35
18: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,决策值:woman
18:19:50.202 [main] INFO  o.d.p.c.s.DecisionTreeBaseService - 决策树:1,用户 id:马冬梅,以后节点 id:12,下一节点 id:122,决策名:age,决策值:40
18: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

退出移动版