组合 Composite
组合模式是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们。
将对象组合成树形结构以表示整个部分的层次结构。组合模式可以让用户统一对待单个对象和对象的组合。
为什么要使用?
组合模式的对象职责:
将对象组合成树状结构,并且能像使用独立对象一样使用它们。
组合模式就是一种容器与内容组合在一起的模式。它能够使容器与内容具有一致性。
例如,在文件系统中,有文件夹📁和文件📄两种对象,它们间的层级关系就是树状关系🌲。文件夹属于容器、文件属于内容,但是它们都属于目录条目。所以我们能像使用独立对象一样使用它们。
那么在树状结构中,树节点就是容器🫙,能够承载更多的树节点和叶子节点,叶子节点就是内容。它们都属于节点,可以组合成一棵树。两者不同的是树节点会记录它的子节点而已。
如果你有以下需求,可以考虑使用组合模式。
- 希望一组对象按照某种层级结构进行管理。
- 需要按照统一的行为来处理复杂结构中的对象。
- 能够快速拓展对象组合。
模式结构
组件(Component)接口描述了树中简单项目和复杂项目所共有的操作。
叶节点(Leaf)是树的基本结构,它不包含子项目。一般情况下,叶节点最终会完成大部分的实际工作,因为它们无法将工作指派给其他部分。
容器(Container)——又名“组合(Composite)”——是包含叶节点或其他容器等子项目的单位。容器不知道其子项目所属的具体类,它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目,处理中间结果,然后将最终结果返回给客户端。
客户端(Client)通过组件接口与所有项目交互。因此,客户端能以相同方式与树状结构中的简单或复杂项目交互。
组合模式的类图:
模式实现
文件中只包含自己的信息,属于单个对象;文件夹中能够存储文件和文件夹,属于组合对象。该示例使用组合模式将文件和文件夹归为目录条目,使其具有一致性,方便管理。
示例程序的类图
代码实现
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package example;
public interface Entry {
String getName();
int getSize();
void printList(String prefix); }
|
叶节点
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package example;
public class File implements Entry { private String name; private int size;
public File(String name, int size) { this.name = name; this.size = size; }
@Override public String getName() { return name; }
@Override public int getSize() { return size; }
@Override public void printList(String prefix) { System.out.println(prefix + "/" + name); } }
|
容器
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package example;
import java.util.ArrayList;
public class Directory implements Entry { private String name; private ArrayList<Entry> directory = new ArrayList<>();
public Directory(String name) { this.name = name; }
public Entry add(Entry entry) { directory.add(entry); return this; }
@Override public String getName() { return name; }
@Override public int getSize() { return directory.size(); }
@Override public void printList(String prefix) { String path = prefix + "/" + name; System.out.println(path); for (Entry entry : directory) { entry.printList(path); } } }
|
代码测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import example.Directory; import example.File;
public class Test { public static void main(String[] args) { File txt = new File("test.txt", 1); File png = new File("test.png", 10); Directory root = new Directory("root"); Directory child = new Directory("child");
System.out.println("root文件夹添加child文件夹:"); root.add(child).printList("~"); System.out.println("文件数量:" + root.getSize());
System.out.println("\nroot文件夹添加test.png文件:"); root.add(png).printList("~"); System.out.println("文件数量:" + root.getSize());
System.out.println("\nchild文件夹添加test.txt文件:"); child.add(txt).printList("~/root"); System.out.println("文件数量:" + child.getSize()); } }
|
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| root文件夹添加child文件夹: ~/root ~/root/child 文件数量:1
root文件夹添加test.png文件: ~/root ~/root/child ~/root/test.png 文件数量:2
child文件夹添加test.txt文件: ~/root/child ~/root/child/test.txt 文件数量:1
|
你也可以在 Entry
接口中加入获取父类文件路径的方法,就无需在创建 File
和 Directory
时,直接输入字符串常量。
常用场景和解决方案
- 处理一个树型结构。比如,公司人员组织架构、订单信息等。
- 跨越多个层次结构聚合数据。比如,统计文件夹下文件总数。
- 统一处理一个结构中的多个对象。比如,遍历文件夹下所有
XML
类型文件内容。
- 如果你希望客户端代码以相同方式处理简单和复杂元素,可以使用该模式。组合模式中定义的所有元素共用同一个接口。在这一接口的帮助下,客户端不必在意其所使用的对象的具体类。
模式的优缺点
优点 |
缺点 |
你可以利用多态和递归机制更方便地使用复杂树结构。 |
对于功能差异较大的类,提供公共接口或许会有困难。在特定情况下,你需要过度一般化组件接口,使其变得令人难以理解。 |
开闭原则。无需更改现有代码,你就可以在应用中添加新元素,使其成为对象树的一部分。 |
|
使用组合模式的优势
- 清晰定义分层结构。
- 快速新增节点,提升组合灵活性。
- 简化了使用者使用复杂结构数据的代码。组合模式为你提供了两种共享公共接口的基本元素类型:简单叶节点和复杂容器。容器中可以包含叶节点和其他容器。这使得你可以构建树状嵌套递归对象结构。
使用组合模式的劣势
- 难以限制节点类型。
- 需要增加很多运行时的检查,增加了代码复杂度。
- 错误的遍历算法可能会影响系统性能。
拓展知识
- 你可以在创建复杂组合树时使用生成器模式,因为这可使其构造步骤以递归的方式运行。
- 责任链模式通常和组合模式结合使用。在这种情况下,叶组件接收到请求后,可以将请求沿包含全体父组件的链一直传递至对象树的底部。
- 你可以使用迭代器模式来遍历组合树。
- 你可以使用访问者模式对整个组合树执行操作。
- 你可以使用享元模式实现组合树的共享叶节点以节省内存。
🔙 设计模式
📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!
🔗参考文献:
🌐 设计模式 –refactoringguru
▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育
📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1