树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等。
组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)。
1. 定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
2. 结构
在组合模式中引入了抽象构件类Component,它是所有容器类和叶子类的公共父类,客户端针对Component进行编程。组合模式结构如图所示:
- Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
- Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
3. 代码实现
代码实现了一个公司的组织架构,包括各级公司,各级公司包括各部门,AbstractOrganization充当抽象构建类,Company充当容器构建类(可以定义其他容器构建类),Department充当叶子构建类(可以定义其他叶子构建类)。
AbstractOrganization
public abstract class AbstractOrganization {
public abstract void add(AbstractOrganization organization);
public abstract void remove(AbstractOrganization organization);
public abstract AbstractOrganization getChild(int i);
public abstract void notifyMessage();
}
Company
public class Company extends AbstractOrganization {
private List<AbstractOrganization> organizationList=new ArrayList<>();
private String name;
public Company(String name) {
this.name = name;
}
@Override
public void add(AbstractOrganization organization) {
organizationList.add(organization);
}
@Override
public void remove(AbstractOrganization organization) {
organization.remove(organization);
}
@Override
public AbstractOrganization getChild(int i) {
return organizationList.get(i);
}
@Override
public void notifyMessage() {
System.out.println("对公司:"+name+" 进行通知");
for (AbstractOrganization organization:organizationList){
organization.notifyMessage();
}
}
}
Department
public class Department extends AbstractOrganization {
private String name;
public Department(String name) {
this.name = name;
}
@Override
public void add(AbstractOrganization organization) {
System.out.println("对不起,不支持该方法!");
}
@Override
public void remove(AbstractOrganization organization) {
System.out.println("对不起,不支持该方法!");
}
@Override
public AbstractOrganization getChild(int i) {
System.out.println("对不起,不支持该方法!");
return null;
}
@Override
public void notifyMessage() {
System.out.println("对"+name+" 进行通知");
}
}
Client
public class Client {
public static void main(String[] args) {
AbstractOrganization c1,c2,d1,d2,d3;
c1=new Company("总公司");
c2=new Company("分公司1");
d1=new Department("总公司部门1");
d2=new Department("分公司部门1");
d3=new Department("分公司部门2");
c1.add(c2);
c1.add(d1);
c2.add(d2);
c2.add(d3);
//客户端无序关心节点的层次结构,对节点可以进行统一处理
c1.notifyMessage();
System.out.println("-------------");
c2.notifyMessage();
}
}
//对公司:总公司 进行通知
//对公司:分公司1 进行通知
//对分公司部门1 进行通知
//对分公司部门2 进行通知
//对总公司部门1 进行通知
//-------------
//对公司:分公司1 进行通知
//对分公司部门1 进行通知
//对分公司部门2 进行通知
4. 透明组合模式与安全组合模式
4.1 透明组合模式
透明组合模式中,抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove()以及getChild()等方法,这样做的好处是确保所有的构件类都有相同的接口。在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以相同地对待所有的对象。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供add()、remove()以及getChild()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。
4.1 安全组合模式
Java AWT中使用的组合模式就是安全组合模式。
安全组合模式中,在抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法。这种做法是安全的,因为根本不向叶子对象提供这些管理成员对象的方法,对于叶子对象,客户端不可能调用到这些方法。
安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
客户端需要指定具体的容器类型,才能调用管理成员对象的方法。
Comapany c1,c2;
AbstractOrganization d1,d2,d3;
...
c1.notifyMessage();
5. 优缺点
- 优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,简化客户端代码。
- 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
- 缺点
- 在增加新构件时很难对容器中的构件类型进行限制。因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。
6. 适用场景
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
- 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
- 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
7. 个人理解
组合模式用户处理类似树形结构的包含容器对象和叶子对象的层次结构,通过该模式可忽略整体与部分的差异,让客户端统一对待它们,同时符合“开闭原则”利于扩展。