定义
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
结构和说明
示例代码
public class ComponentDemo {
/**
* 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
*/
public static abstract class Component {
/**
* 示意方法,子组件对象可能有的功能方法
*/
public abstract void someOperation();
/**
* 向组合对象中加入组合对象
*
* @param child 被加入组合对象中的组件对象
* @throws UnsupportedOperationException
*/
public void addChild(Component child) throws UnsupportedOperationException {
// 缺省的实现,抛出例外,因为叶子对象没有这个功能
throw new UnsupportedOperationException("对象不支持这个功能");
}
/**
* 从组合对象中移除某个组件对象
*
* @param child 被移除的组件对象
* @throws UnsupportedOperationException
*/
public void removeChild(Component child) throws UnsupportedOperationException {
// 缺省的实现,抛出例外,因为叶子对象没有这个功能
throw new UnsupportedOperationException("对象不支持这个功能");
}
/**
* 返回某个索引对应的组件对象
*
* @param index 需要获取组件对象的索引,索引从 0 开始
* @return 索引对应的组件对象
* @throws UnsupportedOperationException
*/
public Component getChildren(int index) throws UnsupportedOperationException {
// 缺省的实现,抛出例外,因为叶子对象没有这个功能
throw new UnsupportedOperationException("对象不支持这个功能");
}
}
/**
* 组合对象,通常需要存储子对象,定义有子部件的部件行为
* 并实现在 Component 里面定义的与子部件有关的操作
*/
public static class Composite extends Component {
/**
* 用来存储组合对象中包含的子组件对象
*/
private List<Component> childComponents = new ArrayList<>();
/**
* 示意方法,通常在里面需要实现递归的调用
*/
@Override
public void someOperation() {
// 递归地进行子组件响应方法的调用
childComponents.parallelStream().forEach(Component::someOperation);
}
@Override
public void addChild(Component child) throws UnsupportedOperationException {
childComponents.add(child);
}
@Override
public void removeChild(Component child) throws UnsupportedOperationException {
childComponents.remove(child);
}
@Override
public Component getChildren(int index) throws UnsupportedOperationException {
return childComponents.get(index);
}
}
/**
* 叶子对象,叶子对象不再包含其他子对象
*/
public static class Leaf extends Component {
/**
* 示意方法,叶子对象可能有自己的功能方法
*/
@Override
public void someOperation() {
// do something
}
}
public static class Client {
public static void main(String[] args) {
// 定义多个 Composite 对象
Component root = new Composite();
Component c1 = new Composite();
Component c2 = new Composite();
// 定义多个叶子对象
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Component leaf3 = new Leaf();
// 组合成为树形的对象结构
// 并不是每次操作组件对象都需要组装树形的对象结构,
// 如果对象结构已经存在,直接操作就可以了
root.addChild(c1);
root.addChild(c2);
root.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
// 操作 Component 对象
root.someOperation();
}
}
}
优缺点
优点
- 定义了包含基本对象和组合对象的类层次结构
在组合模式中,基本对象可以被组合成复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构。
- 统一了组合对象和叶子对象
在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。
- 简化了客户端的调用
组合模式通过统一组合对象和叶子对象,使得客户端在使用它们的时候,不需要再去区分它们,客户不关心使用的到底是什么类型的对象,这就大大简化了客户端的使用。
- 更容易扩展
由于客户端是统一地面对 Component 来操作,因此,新定义的 Composite 或 Leaf 子类能够很容易地与已有的结构一起工作,而客户端不需要为了增添了新的组件类而改变。
缺点
- 很难限制组合中的组件类型。
这在需要检测组件类型的时候,使得我们不能依靠编译期的类型约束来完成,必须要在运行期间动态检测。
思考
本质
统一叶子对象和组合对象。
何时选用
- 如果你想表示对象的部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也容易。
- 如果你希望统一地使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。
相关模式
- 组合模式和装饰模式
这两个模式可以组合使用。
装饰模式在组装多个装饰器对象的时候,是一个装饰器找下一个装饰器,下一个再找下一个,如此递归下去。其实这种结构也可以使用组合模式来帮助构建,这样一来,装饰器模式就相当于组合模式的 Composite 对象了。
要让两个模式很好的组合使用,通常会让它们有一个公共的父类。因此装饰器必须支持组合模式需要的一些功能,比如增加,删除子组件等。
- 组合模式和享元模式
这两个模式可以组合使用。
如果组合模式中出现大量相似的组件对象的话,可以考虑使用享元模式来帮助缓存组件对象,这样可以减少对内存的需要。
使用享元模式也是有条件的,如果组件对象的可变化部分能够从组件对象中分离出去,并且组件对象本身不需要向父组件发送请求的话,就可以采用享元模式。
- 组合模式和迭代器模式
这两个模式可以组合使用。
在组合模式中,通常可以使用迭代器模式来遍历组合对象的子对象集合,而无需关心具体存放子对象的聚合结构。
- 组合模式和访问者模式
这两个模式可以组合使用。
访问者模式能够在不修改原有对象结构的情况下,为对象结构中的对象添加新的功能,访问者模式和组合模式合用,可以把原本分散在 Composite 和 Leaf 类中的操作和行为都局部化。
如果在使用组合模式的时候,预计到今后会有添加其他功能的可能,那么可以采用访问者模式,来预留好添加新功能的方式和通道,这样以后在添加新功能的时候,就不需要在修改已有的对象结构和已经实现的功能了。
- 组合模式和职责链模式
这两个模式可以组合使用。
职责链模式要解决的问题是:实现请求的发送者和接收者之间的接耦。职责链模式的实现方式是把多个接收者组合起来,构成职责链,然后让请求在这条链上传递,直到有接收者处理这个请求为止。
可以应用组合模式来构建这条链,相当于子组件找父组件,父组件又找父组件,如此递归下去,构成一条处理请求的组件对象链。
- 组合模式和命令模式
这两个模式可以组合使用。
命令模式中有一个宏命名的功能,通常这个宏命令就是使用组合模式来组装出来的。