模式说明
组合模式并不是我们常说的组合优于继承的那个组合,而是将一组对象组织成树状结构。组合模式往往能够简化复杂的树结构的编码,而且在扩展节点对象时能够更加游刃有余,但如果树结构层次较低,节点较少,相对简单的情况下使用组合模式就是大材小用了。
应用场景
什么场景适合使用组合模式?只要能够构建树状结构的数据就可以使用组合模式。如部门的层级结构,通过部门 ID 和父部门 ID 关联成一课树状结构。还有,XML 格式的数据反序列化为一组对象也符合树状结构,这是因为存在 XML 对象嵌套关系,另外 LDAP 里的用户及组织架构也是树状结构。
组合模式的关系如下图 1 所示,所有的节点都将会继承 Node 对象或实现 Node 接口,而 NodeImpl 中将包含一组 Node 集合,注意这里的引用有可能存在闭环,即互相引用导致死循环,这是组合模式中的其中一个缺点,另外,注意到 Leaf 叶子是可选节点。
图 1
_
如果想要解决组合模式的相互引用导致的遍历时的死循环问题,可以增加 Set<Node> visited
记录已访问的节点,不过 Node 就需要增加 hash() / equal() 方法了,又或者使用 Node.hash() 返回值作为已访问的记录,如 Set<Integer> visited
,只需要记录类在内存中的地址 visited.add(Node.hash())。
代码实现
现在,通过组合模式实现 XML 的构建,代码如下。
public interface Node {
String name();
Map<String, String> attrs();
List<Node> children();
}
public class ElementNode implements Node {
private final String name;
private Map<String, String> attrs;
private List<Node> children;
public ElementNode(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public Map<String, String> attrs() {
return Optional.of(attrs).orElse(Collections.emptyMap());
}
@Override
public List<Node> children() {
return Optional.of(children).orElse(Collections.emptyList());
}
public void setAttrs(String key, String val) {
if (attrs == null) attrs = new HashMap<>();
if (val == null) { // 表示删除
attrs.remove(key);
return;
}
attrs.put(key, val);
}
public void addChild(Node n) {
if (children == null) children = new LinkedList<>();
children.add(n);
}
}
Node html = new ElementNode("html");
Node head = new ElementNode("head");
head.addChild(new ElementNode("script"));
Node body = new ElementNode("body");
body.addChild(new ElementNode("div"));
html.addChild(head);
html.addChild(body);
/**
大概输出的就是如下的样子
<html>
<head>
<script></script>
</head>
<body>
<div></div>
</body>
</html>
*/