一、组合模式介绍
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 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 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 userData);
/**
* 过滤出节点决策
* @param decisionValue 决策值
* @param treeNodeDecisionList 节点决策列表
* @return
*/
Long filterDecisionNode(String decisionValue, List treeNodeDecisionList);
}
逻辑基础决策器
/**
* 逻辑基础决策器
*/
public abstract class LogicBaseDecision implements LogicDecision {
@Override
public abstract String getDecisionValue(Long treeId, String userId, Map userData);;
@Override
public Long filterDecisionNode(String decisionValue, List 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 userData) {
return userData.get("gender");
}
}
用户年龄决策器
/**
* 用户年龄决策器
*/
public class UserAgeDecision extends LogicBaseDecision {
@Override
public String getDecisionValue(Long treeId, String userId, Map 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 userData);
}
决策树基础服务
/**
* 决策树基础服务
*/
public abstract class DecisionTreeBaseService implements DecisionTreeService {
private final Logger log = LoggerFactory.getLogger(DecisionTreeBaseService.class);
/**
* 决策map
*/
protected Map logicDecisionMap = new ConcurrentHashMap<>();
@Override
public abstract DecisionResult process(Tree tree, String userId, Map userData);
/**
* 寻找叶子节点
* @param tree 决策树
* @param userId 用户id
* @param userData 用户数据
* @return
*/
protected TreeNode findLeafNode(Tree tree, String userId, Map 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 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 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 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 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 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 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