✍ 对于 树形结构 ,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。 (递归调用)。由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须 有区别地对待容器对象和叶子对象
,而实际上 大多数情况下客户端希望一致地处理它们 , 因为对于这些对象的区别对待将会使得程序非常复杂
组合模式描述了如何将容器对象和叶子对象进行递归组合
,使得用户在使用时无须对它们进行区分
,可以 一致地对待容器对象和叶子对象
,这就是组合模式的模式动机。
给组合模式下个定义的话如下:
组合模式(Composite Pattern) :组合多个对象形成 树形结构
以表示“整体- 部分”的结构层次
。组合模式对单个对象(即叶子对象)
和 组合对象(即容器对象)
的使用具有一致性 。组合模式又可以称为“整体- 部分”(Part-Whole) 模式
,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系 。
组合模式包含如下角色:
• Component: 抽象构件: 它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由叶子构件完成。
• Leaf: 叶子构件 :是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
• Composite: 容器构件:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
✍ 演示一下:
创建目录组件的抽象类( Component: 抽象构件)
//目录组件
public abstract class CatalogComponent {
public void add(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持删除操作");
}
public String getName(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取名称操作");
}
public double getPrice(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取价格操作");
}
public void print(){
throw new UnsupportedOperationException("不支持打印操作");
}
}
创建继承该类的课程(相当于菜单) (Leaf: 叶子构件 )
public class Course extends CatalogComponent {
private String name; //课程名称
private double price; //课程价格
public Course(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public double getPrice(CatalogComponent catalogComponent) {
return this.price;
}
@Override
public void print() {
System.out.println("Course Name:"+name+" Price:"+price);
}
}
创建继承抽象类的目录( Composite: 容器构件)
import java.util.ArrayList;
import java.util.List;
public class CourseCatalog extends CatalogComponent {
//目录组件
private List<CatalogComponent> items = new ArrayList<CatalogComponent>();
private String name; //目录名称
private Integer level; //目录级别
public CourseCatalog(String name,Integer level) {
this.name = name;
this.level = level;
}
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public void print() {
System.out.println(this.name);
for(CatalogComponent catalogComponent : items){
if(this.level != null){
for(int i = 0; i < this.level; i++){
System.out.print(" ");
}
}
catalogComponent.print();
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
CatalogComponent linuxCourse = new Course("Linux课程",11);
CatalogComponent windowsCourse = new Course("Windows课程",11);
CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录",2);
CatalogComponent mmallCourse1 = new Course("Java网络编程",55);
CatalogComponent mmallCourse2 = new Course("Java编程思想",66);
CatalogComponent designPattern = new Course("Java设计模式",77);
javaCourseCatalog.add(mmallCourse1);
javaCourseCatalog.add(mmallCourse2);
javaCourseCatalog.add(designPattern);
CatalogComponent imoocMainCourseCatalog = new CourseCatalog("计算机课程主目录",1);
imoocMainCourseCatalog.add(linuxCourse);
imoocMainCourseCatalog.add(windowsCourse);
imoocMainCourseCatalog.add(javaCourseCatalog);
imoocMainCourseCatalog.print();
}
}
结果:
UML图:
这里屏蔽了细节 很容易发现树形结构
CatalogComponent是抽象类
CourseCatalog是抽象类的实现类 负责了目录的级别
Course也是抽象类的实现类,负责了菜单
目录界别 加 菜单内容 组合成目录
这里起到关键作用的是 抽象类 和分离组合的思想
看一下没有屏蔽细节的UML
换一种角度
组合模式的关键是 定义了一个抽象构件类
, 它既可以代表叶子 , 又可以代表容器 , 而 客户端针对该抽象构件类进行编程
, 无须知道它到底表示的是叶子还是容器 , 可以对其进行统一处理 。 同时 容器对象与抽象构件类之间还建立一个聚合关联关系
, 在容器对象中既可以包含叶子 , 也可以包含容器 , 以此 实现递归组合 , 形成一个树形结构
✍ 说一下它的优缺点:
组合模式的优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
- 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
组合模式的缺点
- 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
- 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制。
✍ 在以下情况下可以使用组合模式:
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
- 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。
✍ 它的应用还是比较广泛的:
XML解析;
操作系统中的目录结构 是一个树形结构,因此在对文件和文件夹进行操作时可以应用组合模式,例如杀毒软件在查毒或杀毒时,既可以针对一个具体文件,也可以针对一个目录。如果是对目录查毒或杀毒,将递归处理目录中的每一个子目录和文件。
JDK 的AWT/Swing 是组合模式在Java 类库中的一个典型实际应用。