一、组合模式介绍
1. 解决的问题
在树形构造的问题中,含糊了简略元素和简单元素的概念,客户端能够像解决简略元素一样来解决简单元素,从而使客户端和简单元素的外部元素解耦。
2. 定义
组合元素是一种结构型设计模式,能够应用它将对象组合成树状构造,并能像应用独立对象一样应用它们。
3. 利用场景
- 须要实现树状对象构造,能够应用组合模式。
- 心愿客户端代码以雷同形式解决简略和简单元素,能够应用组合模式。
二、组合模式优缺点
1. 长处
- 能够利用多态和递归机制更不便地应用简单树结构。
- 开闭准则:无需更改现有代码,就能够在利用中增加新元素,使其成为对象树的一部分。
2. 毛病
- 对于性能差别较大的类,提供公共接口会有艰难。在特定状况下,须要过渡一般化组件接口,使其变得令人难以了解。
三、组合模式利用实例:决策树实现精准化经营
1. 实例场景
精准化经营在咱们的业务开发中越来越重要,产品往往会依据性别、年龄、查看偏好等条件来决定给不同范畴的用户推送不同的音讯,目前就以该场景来实现组合场景。
2. 组合模式实现
本次组合模式的实现思维是依靠决策树的模式。
根底概念
决策树记录了以后根节点和全副节点列表。
非叶子节点蕴含策略名、节点策略列表。
节点策略蕴含下一个字节点、以后决策类型(大于、等于等条件)、以后决策类型。
叶子节点定义了最终的策略,如推送二次元文章、推送财经类文章等。
决策树遍历步骤:
- 当发现节点为非叶子节点时,依据策略名获取以后策略。
- 依据策略数据遍历节点策略列表,判断满足哪种节点策略。
- 依据满足的节点策略获取下一个节点。
- 当发现节点为叶子节点,返回节点数据,遍历完结。
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
四、组合模式构造
- 组件(Component)接口形容了树中简略我的项目和简单我的项目所共有的操作。
- 叶节点(Leaf)是树的根底构造,它不蕴含子项目。
个别状况下,叶节点最终会实现大部分的理论工作,因为它们无奈把工作委派给其余局部。
- 容器(Container)——又名“组合(Composite)”——是蕴含叶节点或其余容器等子项目的单位。容器不晓得其子我的项目所属的具体类,它只通过通用的组件接口与其子项目交互。
容器接管到申请会将工作调配给本人的子项目,解决两头后果,而后将最终后果返回给客户端。
- 客户端(Client)通过组件接口与所有我的项目交互。因而,客户端能以雷同形式与树状构造中的简略或简单我的项目交互。
设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。
源码地址:https://github.com/yiyufxst/design-pattern-java参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深刻设计模式:https://refactoringguru.cn/design-patterns/catalog