组合


将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

组合模式(Composite)经常用于树形结构,为了简化代码,使用Composite可以把一个叶子节点与一个父节点统一起来处理。

我们来看一个具体的例子。在XML或HTML中,从根节点开始,每个节点都可能包含任意个其他节点,这些层层嵌套的节点就构成了一颗树。

要以树的结构表示XML,我们可以先抽象出节点类型Node

  1. public interface Node {
  2. // 添加一个节点为子节点:
  3. Node add(Node node);
  4. // 获取子节点:
  5. List<Node> children();
  6. // 输出为XML:
  7. String toXml();
  8. }

对于一个<abc>这样的节点,我们称之为ElementNode,它可以作为容器包含多个子节点:

  1. public class ElementNode implements Node {
  2. private String name;
  3. private List<Node> list = new ArrayList<>();
  4. public ElementNode(String name) {
  5. this.name = name;
  6. }
  7. public Node add(Node node) {
  8. list.add(node);
  9. return this;
  10. }
  11. public List<Node> children() {
  12. return list;
  13. }
  14. public String toXml() {
  15. String start = "<" + name + ">\n";
  16. String end = "</" + name + ">\n";
  17. StringJoiner sj = new StringJoiner("", start, end);
  18. list.forEach(node -> {
  19. sj.add(node.toXml() + "\n");
  20. });
  21. return sj.toString();
  22. }
  23. }

对于普通文本,我们把它看作TextNode,它没有子节点:

  1. public class TextNode implements Node {
  2. private String text;
  3. public TextNode(String text) {
  4. this.text = text;
  5. }
  6. public Node add(Node node) {
  7. throw new UnsupportedOperationException();
  8. }
  9. public List<Node> children() {
  10. return List.of();
  11. }
  12. public String toXml() {
  13. return text;
  14. }
  15. }

此外,还可以有注释节点:

  1. public class CommentNode implements Node {
  2. private String text;
  3. public CommentNode(String text) {
  4. this.text = text;
  5. }
  6. public Node add(Node node) {
  7. throw new UnsupportedOperationException();
  8. }
  9. public List<Node> children() {
  10. return List.of();
  11. }
  12. public String toXml() {
  13. return "<!-- " + text + " -->";
  14. }
  15. }

通过ElementNodeTextNodeCommentNode,我们就可以构造出一颗树:

  1. Node root = new ElementNode("school");
  2. root.add(new ElementNode("classA")
  3. .add(new TextNode("Tom"))
  4. .add(new TextNode("Alice")));
  5. root.add(new ElementNode("classB")
  6. .add(new TextNode("Bob"))
  7. .add(new TextNode("Grace"))
  8. .add(new CommentNode("comment...")));
  9. System.out.println(root.toXml());

最后通过root节点输出的XML如下:

  1. <school>
  2. <classA>
  3. Tom
  4. Alice
  5. </classA>
  6. <classB>
  7. Bob
  8. Grace
  9. <!-- comment... -->
  10. </classB>
  11. </school>

可见,使用Composite模式时,需要先统一单个节点以及“容器”节点的接口:

  1. ┌───────────┐
  2. Node
  3. └───────────┘
  4. ┌────────────┼────────────┐
  5. ┌───────────┐┌───────────┐┌───────────┐
  6. ElementNode││ TextNode ││CommentNode
  7. └───────────┘└───────────┘└───────────┘

作为容器节点的ElementNode又可以添加任意个Node,这样就可以构成层级结构。

类似的,像文件夹和文件、GUI窗口的各种组件,都符合Composite模式的定义,因为它们的结构天生就是层级结构。

练习

组合 - 图1下载练习:使用Composite模式构造XML (推荐使用IDE练习插件快速下载)

小结

Composite模式使得叶子对象和容器对象具有一致性,从而形成统一的树形结构,并用一致的方式去处理它们。

读后有收获可以支付宝请作者喝咖啡:

组合 - 图2