1)意图
将对象组合成树型结构以表示“部分-整体”的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。
2)结构
组合模式的结构如图 7-33 所示。
其中:
- Component 为组合中的对象声明接口;在适当情况下实现所有类共有接口的默认行为;声明一个接口用于访问和管理 Component 的子组件;(可选)在递归结构中定义一个接口,用于访问一个父组件,并在合适的情况下实现它。
- Leaf在组合中表示叶结点对象,叶结点没有子结点:在组合中定义图元对象的行为。
- Composite 定义有子组件的那些组件的行为;存储子组件;在Component 接口中实现与子组件有关的操作。
- Client 通过 Component 接口操纵组合组件的对象。
3)适用性
Composite 模式适用于:
- 想表示对象的部分-整体层次结构。
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
4 应用举例
示例:文件系统中的目录和文件
创建了一个简单的文件系统模型,其中Directory可以包含其他Directory或File,而File则不能包含其他元素。通过这种方式,可以很容易地构建出复杂的文件系统结构,并且客户端可以通过相同的接口操作单个文件或整个目录树。
1. Component (组件)
public abstract class FileSystemEntry {
protected String name;
public FileSystemEntry(String name) {
this.name = name;
}
public abstract int getSize();
public abstract void printList(String prefix);
}
在这个例子中,FileSystemEntry
类充当了组件的角色。它是所有文件系统条目的基类,无论是文件还是目录。这个类定义了一些公共的行为,比如 getSize()
和 printList(String prefix)
方法,这些方法在所有的文件系统条目中都是通用的。
getSize()
:返回文件或目录的大小。对于文件,它直接返回文件的大小;对于目录,它需要计算所有子项的总大小。printList(String prefix)
:打印出文件或目录的信息,以及它们在文件系统中的路径。这里的prefix
参数是用来构建路径的,当打印一个目录时,会递归地调用其子项的printList()
方法,从而构建出完整的路径。
2. Leaf (叶子节点 - 文件)
public class File extends FileSystemEntry {
private int size;
public File(String name, int size) {
super(name);
this.size = size;
}
@Override
public int getSize() {
return size;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
@Override
public String toString() {
return "File (" + name + ", " + size + " bytes)";
}
}
File
类是叶子节点的具体实现。它代表文件系统中的一个文件,不包含任何子节点。因此,它的 add()
和 remove()
操作是没有意义的,所以在 File
类中不需要实现这些方法。File
类主要实现了 getSize()
和 printList(String prefix)
方法,分别用来返回文件的大小和打印文件信息。
3. Composite (组合节点 - 目录)
import java.util.ArrayList;
import java.util.List;
public class Directory extends FileSystemEntry {
private List<FileSystemEntry> children = new ArrayList<>();
public Directory(String name) {
super(name);
}
public void add(FileSystemEntry entry) {
children.add(entry);
}
public void remove(FileSystemEntry entry) {
children.remove(entry);
}
@Override
public int getSize() {
int totalSize = 0;
for (FileSystemEntry child : children) {
totalSize += child.getSize();
}
return totalSize;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
String newPrefix = prefix.isEmpty() ? name : prefix + "/" + name;
for (FileSystemEntry child : children) {
child.printList(newPrefix);
}
}
@Override
public String toString() {
return "Directory (" + name + ", " + getSize() + " bytes)";
}
}
Directory
类是组合节点的具体实现。它代表文件系统中的一个目录,可以包含多个文件和其他目录。Directory
类中有一个 children
列表,用于存储目录下的所有子节点。
add(FileSystemEntry entry)
:向目录中添加一个新的子节点。remove(FileSystemEntry entry)
:从目录中移除一个子节点。getSize()
:遍历children
列表,累加每个子节点的大小,最终返回目录的总大小。printList(String prefix)
:首先打印当前目录的信息,然后递归地调用每个子节点的printList(String prefix)
方法,构建并打印出子节点的完整路径。
4. Client (客户端)
public class Client {
public static void main(String[] args) {
// 创建文件和目录
File file1 = new File("file1.txt", 1024);
File file2 = new File("file2.txt", 2048);
Directory dir1 = new Directory("dir1");
Directory dir2 = new Directory("dir2");
// 添加文件到目录
dir1.add(file1);
dir1.add(file2);
dir2.add(dir1);
// 打印目录结构
dir2.printList("");
}
}
在 Client
类的 main
方法中,我们创建了几个文件和目录,并将它们组织成一个树形结构。通过调用 printList("")
方法,我们可以看到整个文件系统的结构和每个文件或目录的大小。
这段代码创建了一个简单的文件系统模型,其中Directory
可以包含其他Directory
或File
,而File
则不能包含其他元素。通过这种方式,可以很容易地构建出复杂的文件系统结构,并且客户端可以通过相同的接口操作单个文件或整个目录树。
运行结果
假设我们运行上面的客户端代码,输出可能如下所示:
/Directory (dir2, 3072 bytes)
/dir2/Directory (dir1, 3072 bytes)
/dir2/dir1/File (file1.txt, 1024 bytes)
/dir2/dir1/File (file2.txt, 2048 bytes)
这表明:
dir2
是最外层的目录,总大小为 3072 字节。dir2
下有一个子目录dir1
,dir1
的总大小也是 3072 字节。dir1
包含两个文件file1.txt
和file2.txt
,大小分别为 1024 字节和 2048 字节。
组合模式的关键在于它可以让你以一致的方式处理单个对象和对象的组合。这意味着你可以用相同的方式来操作文件和目录,而不需要关心它们是单个文件还是包含多个子项的目录。这种设计模式在处理具有层次结构的数据时非常有用,例如文件系统、图形用户界面的组件树等。