对象容器的问题
在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即他们在充当对象的同时,又是其他对象的容器。
public class SingleBox extends IBox{
public void process() {....}
}
public class ContainerBox extends IBox{
public void process() {....}
public ArrayList getBoxes(){....}
}
//如果我们要对这样的对象容器进行处理:
IBox box = Factory.getBox();
if(box instanceof ContainerBox){
box.process();
ArrayList list = ((ContrainerBox)box).getBoxes();//包含递归
}else if (box instanceof SingleBox){
box.process();
}
动机(Motivation)
上述描述的问题根源在于:客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
意图(Intent)
将对象组合成树形结构以表示“部分-整体”的从层次结构,使得用户对单个对象和组合对象的使用具有一致性。
结构(Structure)
-
Component 抽象构件角色
-
Leaf 叶子构件
-
Composite 树枝构件
Composite模式的几个要点
-
Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
-
将“客户代码与复杂的对象容器结构:解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能”应对变化“。
-
Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性“”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
-
Composite模式是在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
示例代码(透明模式)
抽象构件:
public abstract class Component {
//个体和整体都具有的共享
public void doSomething() {}
//增加一个叶子构件或树枝构件
public abstract void add(Component component);
//删除一个叶子构件或树枝构件
public abstract void remove(Component component);
//获得分支下的所有叶子构件和树枝构件
public abstract ArrayList<Component) getChildren();
}
树叶节点:
public class Leaf extends Component {
@Deprecared
public void add(Component component) throws Exception{
//空实现,直接抛出一个“不支持请求”的异常
throw new UnsupportedOperationException();
}
@Deprecared
public void remove(Component component) throws Exception{
//空实现
throw new UnsupportedOperationException();
}
@Deprecared
public ArrayList<Component> getChildren() throws Exception{
//空实现
throw new UnsupportedOperationException();
}
}
树枝构件:
public class Composite extends Component {
//构件容器
private ArrayList<Component> list = new ArrayList<Component>();
//增加一个叶子构件或树枝构件
public void add(Component component){
this.list.add(component);
}
//删除一个叶子构件或树枝构件
public abstract void remove(Component component){
this.list.remove(component);
}
//获得分支下的所有叶子构件和树枝构件
public abstract ArrayList<Component) getChildren(){
return this.list;
}
}
场景类:
public class Client {
public static void main(String[] args) {
//创建一个根节点
Composite root = new Composite();
root.doSomething();
//创建一个树枝构件
Composite branch = new Composite();
//创建一个叶子节点
Leaf leaf = new Leaf();
//建立整体
road.add(branch);
branch.add(leaf);
}
//通过递归遍历树
public staic void display(Component root) {
for(Component c : root.getChildren()){
if(c instanceof Leaf){
c.doSomething();
}else{
//如果是安全模式,传进来的参数是Composite类型,遍历的时候转成了Component类型,所以需要在c前面进行强制转换(Composite)c
display(c);
}
}
}