模式说明

组合模式并不是我们常说的组合优于继承的那个组合,而是将一组对象组织成树状结构。组合模式往往能够简化复杂的树结构的编码,而且在扩展节点对象时能够更加游刃有余,但如果树结构层次较低,节点较少,相对简单的情况下使用组合模式就是大材小用了。

应用场景

什么场景适合使用组合模式?只要能够构建树状结构的数据就可以使用组合模式。如部门的层级结构,通过部门 ID 和父部门 ID 关联成一课树状结构。还有,XML 格式的数据反序列化为一组对象也符合树状结构,这是因为存在 XML 对象嵌套关系,另外 LDAP 里的用户及组织架构也是树状结构。

组合模式的关系如下图 1 所示,所有的节点都将会继承 Node 对象或实现 Node 接口,而 NodeImpl 中将包含一组 Node 集合,注意这里的引用有可能存在闭环,即互相引用导致死循环,这是组合模式中的其中一个缺点,另外,注意到 Leaf 叶子是可选节点。
image.png
图 1
_
如果想要解决组合模式的相互引用导致的遍历时的死循环问题,可以增加 Set<Node> visited 记录已访问的节点,不过 Node 就需要增加 hash() / equal() 方法了,又或者使用 Node.hash() 返回值作为已访问的记录,如 Set<Integer> visited,只需要记录类在内存中的地址 visited.add(Node.hash())。

代码实现

现在,通过组合模式实现 XML 的构建,代码如下。

  1. public interface Node {
  2. String name();
  3. Map<String, String> attrs();
  4. List<Node> children();
  5. }
  1. public class ElementNode implements Node {
  2. private final String name;
  3. private Map<String, String> attrs;
  4. private List<Node> children;
  5. public ElementNode(String name) {
  6. this.name = name;
  7. }
  8. @Override
  9. public String name() {
  10. return name;
  11. }
  12. @Override
  13. public Map<String, String> attrs() {
  14. return Optional.of(attrs).orElse(Collections.emptyMap());
  15. }
  16. @Override
  17. public List<Node> children() {
  18. return Optional.of(children).orElse(Collections.emptyList());
  19. }
  20. public void setAttrs(String key, String val) {
  21. if (attrs == null) attrs = new HashMap<>();
  22. if (val == null) { // 表示删除
  23. attrs.remove(key);
  24. return;
  25. }
  26. attrs.put(key, val);
  27. }
  28. public void addChild(Node n) {
  29. if (children == null) children = new LinkedList<>();
  30. children.add(n);
  31. }
  32. }
  1. Node html = new ElementNode("html");
  2. Node head = new ElementNode("head");
  3. head.addChild(new ElementNode("script"));
  4. Node body = new ElementNode("body");
  5. body.addChild(new ElementNode("div"));
  6. html.addChild(head);
  7. html.addChild(body);
  8. /**
  9. 大概输出的就是如下的样子
  10. <html>
  11. <head>
  12. <script></script>
  13. </head>
  14. <body>
  15. <div></div>
  16. </body>
  17. </html>
  18. */