一、组合模式介绍
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
@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
四、组合模式构造
- 组件(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