动机
- 对于树形结构,当容器对象(如文件夹)的某
一个方法被调用时,将遍历整个树形结构,寻
找也包含这个方法的成员对象(可以是容器对
象,也可以是叶子对象,如子文件夹和文件)
并调用执行(递归调用) - 由于容器对象和叶子对象在功能上的区别,在
使用这些对象的客户代码中必须有区别地对待
容器对象和叶子对象,而实际上大多数情况下
客户希望一致地处理它们,因为对于这些对象
的区别对待将会使得程序非常复杂 - 组合模式描述了如何将容器对象和叶子对象进
行递归组合,使得用户在使用时无须对它们进
行区分,可以一致地对待容器对象和叶子对象
定义
组合多个对象形成树形结构以表示“整体-部分”
的结构层次。组合模式使客户对单个对象(即
叶子对象)和组合对象(即容器对象)的使用
具有一致性
结构
组合模式的设计类图
组合模式包含的角色:
- Component—抽象构件角色
- 象角色,它给参加组合的对象规定一个接口,这个角色给
出共有的接口及其默认行为 - Leaf—叶子构件角色
- 代表参加组合的树叶对象,一个树叶没有下级的子对象,它
定义出参加组合的原始对象的行为 - Composite—容器构件角色
- 代表参加组合的有子对象的对象,给出树枝构件对象的行为
- Composite类型的对象含有其他Component类型的对象。即:
Composite类型的对象可以含有包含其他的容器(Composite)
类型或叶子(Leaf)类型的对象
分析
- 组合模式的关键是定义了一个抽象构件类
Component,它既可以代表叶子Leaf,又可以代
表容器Composite。客户针对Component类进行
编程,无须知道它到底表示的是叶子还是容器,
可以对其进行统一处理 - 容器对象Composite与抽象构件类Component之
间还建立一个聚合关联关系,在容器对象中既
可以包含叶子,也可以包含容器,以此实现递
归组合,形成一个树形结构
Component — 抽象构件角色代码
public abstract class Component
{
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract Component getChild(int i);
public abstract void operation();
}
** Leaf — 叶子构件角色代码**
public class Leaf extends Component
{
public void add(Component c)
{ // 异 常 处 理 或 错 误 提 示 }
public void remove(Component c)
{ // 异 常 处 理 或 错 误 提 示 }
public Component getChild(int i)
{ // 异 常 处 理 或 错 误 提 示 }
public void operation()
{
... ... // 实 现 代 码
}
}
Composite — 容器构件角色代码
public class Composite extends Component {
private ArrayList list = new ArrayList();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
(Component)list.get(i);
}
public void operation() {
for(Object obj:list) {
((Component)obj).operation(); // 递 归
}
}}
举例
举例:文件目录
- 在计算机文件系统中有目录(或称文件夹),目录里
面有文件或者子目录,在子目录里面还会有其他文件
或子目录 - 虽然目录和文件实际上是两种截然不同的东西,但两
者都可放在目录里面
实现代码
- Entity.java
package composite;
/**
* 条目类<br>
* 对File和Directory一视同仁
*/
public abstract class Entry {
public abstract String getName(); // 取得条目名
public abstract int getSize(); // 取得条目大小
protected abstract void printList(String prefix); // 打印条目信息
/** 新增条目: 当前条目为文件时,不能增加条目,为目录时才可以(重写add) */
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException(); // 默认抛出自定义异常
}
public String toString() { // 输出格式
return getName() + "(" + getSize() + ")";
}
}
- FileTreatmentException.java
package composite;
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
- File.java
package composite;
/** 文件类 */
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
- Directory.java
package composite;
import java.util.Iterator;
import java.util.ArrayList;
/** 目录类 */
public class Directory extends Entry {
private String name; // 目录名称
// ArrayList用于存放条目
private ArrayList<Entry> directory = new ArrayList<Entry>();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
/** 递归计算目录的大小 */
public int getSize() {
int size = 0;
Iterator<Entry> it = directory.iterator();
while (it.hasNext()) {
Entry entry = it.next();
size += entry.getSize();
}
return size;
}
/** 重写父类的add()方法 */
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
/** 递归打印目录信息 */
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator<Entry> it = directory.iterator();
while (it.hasNext()) {
Entry entry = it.next();
entry.printList(prefix + "/" + name);
}
}
}
- Test.java
package composite;
/** 测试类 */
public class Test {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory usrdir = new Directory("usr");
rootdir.add(usrdir);
rootdir.printList("");
System.out.println();
System.out.println("Making user entries...");
Directory zs = new Directory("ZhangShan");
Directory ls = new Directory("LiSi");
usrdir.add(zs);
usrdir.add(ls);
zs.add(new File("diary.html", 100));
zs.add(new File("Composite.javan", 200));
ls.add(new File("memo.tex", 300));
rootdir.printList("");
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
组合模式优点
- 可以清楚地定义分层次的复杂对象,表示对象的全
部或部分层次,使得增加新构件也更容易 - 客户调用简单,客户可以一致地使用组合结构或其
中单个对象 - 定义了包含叶子对象和容器对象的类层次结构,叶
子对象可以被组合成更复杂的容器对象,而这个容
器对象又可以被组合,这样不断递归下去,可以形
成复杂的树形结构 - 更容易在组合体内加入对象构件,客户不必因为加
入了新的对象构件而更改原有代码
组合模式缺点
- 使设计变得更加抽象,对象的业务规则如果很复杂,
则实现组合模式具有很大挑战性,而且不是所有的
适用环境
- 需要表示一个对象整体或部分层次,在具有整
体和部分的层次结构中,希望通过一种方式忽
略整体与部分的差异,可以一致地对待它们 - 让客户能够忽略不同对象层次的变化,客户可
以针对抽象构件编程,无须关心对象层次结构
的细节 - 对象的结构是动态的并且复杂程度不一样,但
客户需要一致地处理它们
方法都与叶子对象子类有关联 - 增加新构件时可能会产生一些问题,很难对容器中
的构件类型进行限制