目的
将对象组合成树形结构以表示“部分整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。
动机
当复杂的组件通过多个简单组件进行组合生成的,一个简单的方法时简单组件定义为元类,由元类组成的类定义为容器类(Container)。
Composite模式描述了使用递归方法,使得用户不必区别对待元对象和容器对象。
Composite模式的关键时一个抽象类,它既可以代表元又可以代表元的容器。
组合对象结构
适用性
表示对象的部分和整体的层次结构
忽略组合对象和单个对象的不同,用户统一地使用组合结构中的所有对象
参与者
- Component
为组合中的对象声明接口。
在适当的情况下,实现所有类共有接口的缺省行为。
声明一个接口用于访问和管理Component的子组件。
在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。 - Leaf
在组合中表示叶节点对象,叶节点没有子节点。
在组合中定义图元对象的行为。 - Composite
定义有子部件的部件的行为。
存储子部件。
在Component接口中实现与子部件有关的操作 - Client
通过Component接口操作组合部件的对象。
效果
定义了包含基本对象和组合对象的类层次结构
基本对象可以被组合合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,在客户代码中,任何用到基本对象的地方都可以用到组合对象。
简化客户代码
客户可以一致的使用组合结构和单个对象。在定义组合的类中不需要写选择语句的函数。
使得更容易增加新类型组件
新定义的Composite或Leaf自雷自动的与已有的结构和客户代码在一起工作,客户程序不需要因新的Component类而改变。
使设计更一般化
容易增加新组件也会产生新问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用组合模式时,你不能依赖类型系统施加这些约束,而必须在运行时进行检查。
实现
- 显式的父部件引用
保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构上移和组件删除,同时父部件引用也支持职责链模式(Chain of Responsibility)
Leaf和Composite类可以继承这个引用以及管理这个引用的操作。
对于父部件引用,必须维护一个不变式。即一个组合的所有子节点以这个组合为父节点。Composite类的add和remove操作,那么所有的子类都可以继承这一方法,并自动维护这一不变式。 - 共享组件
共享组件可以减少对存储的需求。
一个可行的解决办法时为子部件储存多个父部件,但当一个请求在结构中向上传递时,这种方法会导致多义性。享元模式(Flyweight)讨论了如何修改设计以避免将父结构储存在一起的方法。如果子部件可以将一些状态存储在外部,从而不需要向父部件发送请求。 - 最大化Component接口
组合模式的目的之一时使得用户不知道他们正在使用的具体的Leaf和Composite类。为了实现这一目的,Component类应尽可能的多定义一些公共操作。Composite类通常提供方法的缺省实现。而Leaf和Composite的子类可以对它们进行重定义。 - 声明管理子部件的操作
1)在composite类中定义子部件管理操作,安全性高,透明性低。(composite和leaf操作不一致性增强,且转型有安全性问题,不推荐)
提高转型安全性:
在component类中增加获取composite方法。
若自身为组合体,返回自身。否则返回空指针。
2)在Component类定义子部件管理操作,安全性低,透明性高。(leaf存在无意义操作)
提高安全性:
在leaf中利用异常,防止误操作
@Override
void add(Component component) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
void delete() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
-
Component是否应该实现Component列表
叶子类数目很少时,才推荐实现该列表。否则对子结点空间浪费严重。 -
子部件排序
如果需要,设计对子结点的访问和管理接口。 -
使用高速缓冲存储
若对组件遍历查找频繁,Composite类可以缓冲存储对它的子部件的遍历查找相关信息。并且需要实现一个接口判断组合部件起缓冲信息是否有效。 -
删除Component
在没有GC机制的语言中,通常最好由Composite负责删除子结点。但当Leaf为共享组件时,删除leaf时,Leaf对象不会改变。
代码实例
一个关于图形界面的组件实例
Component Class
interface Graphic {
void add(Graphic graphic) throws OperationNotSupportedException;
void delete(Graphic graphic) throws OperationNotSupportedException;
List<Graphic> getChild() throws OperationNotSupportedException;
void draw();
}
Composite Class
public class Picture implements Graphic {
List<Graphic> list;
public void add(Graphic graphic){
list.add(graphic);
}
public void delete(Graphic graphic) {
list.remove(graphic);
}
public void draw(){
for (Graphic graphic : list) {
graphic.draw();
}
}
public List<Graphic> getChild(){
return list;
}
}
Abstract Leaf Class
public abstract class Element implements Graphic {
@Override
public void add(Graphic graphic) throws OperationNotSupportedException {
throw new OperationNotSupportedException();
}
@Override
public void delete(Graphic graphic) throws OperationNotSupportedException {
throw new OperationNotSupportedException();
}
@Override
public List<Graphic> getChild() throws OperationNotSupportedException {
throw new OperationNotSupportedException();
}
}
Concrete Leaf Class
public class Line extends Element{
@Override
public void draw() {
System.out.println("draw a line");
}
}
public class Text extends Element {
@Override
public void draw() {
System.out.println("write some letter\n");
}
}
public class Rectangle extends Element {
@Override
public void draw() {
System.out.println("draw a rectangle\n");
}
}