组合(Composite Pattern)模式:将对象组合成树形结构,以表示“部分-整体”的结构层次,使用户对单个对象和和组合对象的使用具有一致性。
组合模式常用于树形结构,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点

由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,满足“开闭原则”;
缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
示例:
在XML或HTML中,从根节点开始,每个节点都可能包含任意个其他节点,这些层层嵌套的节点就构成了一颗树。
public class CompositeDemo {// 节点接口interface Node{// 添加一个节点为子节点Node add(Node node);// 获取子节点List<Node> children();// 输出为XMLString toXml();}// 容器节点,可以包含任意个子节点static 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();}}// 普通节点static 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 new ArrayList<>();}public String toXml() {return text;}}static 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 new ArrayList<>();}public String toXml() {return "<!-- " + text + " -->";}}public static void main(String[] args) {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());}}
输入如下
<school>
<classA>
Tom
Alice
</classA>
<classB>
Bob
Grace
<!-- comment... -->
</classB>
</school>
