组合模式
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite)经常用于树形结构,为了简化代码,使用Composite可以把一个叶子节点与一个父节点统一起来处理。
我们来看一个具体的例子。在XML或HTML中,从根节点开始,每个节点都可能包含任意个其他节点,这些层层嵌套的节点就构成了一颗树。
要以树的结构表示XML,我们可以先抽象出节点类型Node
:
1 2 3 4 5 6 7 8
| public interface Node { Node add(Node node); List<Node> children(); String toXml(); }
|
对于一个这样的节点,我们称之为
ElementNode
,它可以作为容器包含多个子节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class ElementNode implements Node { private String name; private List<Node> list = new ArrayList<>();
public ElementNode(String name) { this.name = name; }
public Node add(Node node) { list.add(node); return this; }
public List<Node> children() { return list; }
public String toXml() { String start = "<" + name + ">\n"; String end = "</" + name + ">\n"; StringJoiner sj = new StringJoiner("", start, end); list.forEach(node -> { sj.add(node.toXml() + "\n"); }); return sj.toString(); } }
|
对于普通文本,我们把它看作TextNode
,它没有子节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class TextNode implements Node { private String text;
public TextNode(String text) { this.text = text; }
public Node add(Node node) { throw new UnsupportedOperationException(); }
public List<Node> children() { return List.of(); }
public String toXml() { return text; } }
|
此外,还可以有注释节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class CommentNode implements Node { private String text;
public CommentNode(String text) { this.text = text; }
public Node add(Node node) { throw new UnsupportedOperationException(); }
public List<Node> children() { return List.of(); }
public String toXml() { return "<!-- " + text + " -->"; } }
|
通过ElementNode
、TextNode
和CommentNode
,我们就可以构造出一颗树:
1 2 3 4 5 6 7 8 9
| Node root = new ElementNode("school"); root.add(new ElementNode("classA") .add(new TextNode("Tom")) .add(new TextNode("Alice"))); root.add(new ElementNode("classB") .add(new TextNode("Bob")) .add(new TextNode("Grace")) .add(new CommentNode("comment..."))); System.out.println(root.toXml());
|
最后通过root
节点输出的XML如下:
1 2 3 4 5 6 7 8 9 10 11
| <school> <classA> Tom Alice </classA> <classB> Bob Grace
</classB> </school>
|
可见,使用Composite模式时,需要先统一单个节点以及“容器”节点的接口:
1 2 3 4 5 6 7 8 9
| ┌───────────┐ │ Node │ └───────────┘ ▲ ┌────────────┼────────────┐ │ │ │ ┌───────────┐┌───────────┐┌───────────┐ │ElementNode││ TextNode ││CommentNode│ └───────────┘└───────────┘└───────────┘
|
作为容器节点的ElementNode
又可以添加任意个Node
,这样就可以构成层级结构。
类似的,像文件夹和文件、GUI窗口的各种组件,都符合Composite模式的定义,因为它们的结构天生就是层级结构。