一、简介
组合模式是一种结构型模式,允许我们将对象组合成树形结构来表现”部分-整体“的层次结构,同时使得客户能够以一致的方式处理单个对象(叶子对象)和组合结构(容器对象),也就是使用时不需要区分当前对象是叶子对象还是容器对象,因为它们定义了相同的功能。
组合模式中包含三类角色:
- 组合部件(Component):它是一个抽象角色,为要组合的对象(即叶子对象和容器对象)提供统一的接口。因此即可以表示叶子节点也可以表示枝节点。
- 叶子对象(Leaf):在树形结构中表示叶子节点,不能包含子节点
- 容器对象(Composite):在树形结构中表示枝节点,用来存储部件,实现在Component接口中的有关操作,如增加(add)和删除(remove)子部件(即子节点)。
从模式结构中我们看出了叶子对象和容器对象都实现Component接口,这也是能够将叶子对象和容器对象一致对待的关键所在。
优点:
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
- 更容易在组合结构内加入新的对象构件
缺点:
- 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。
- 而且抽象的组合部件中定义的方法并不都是叶子对象需要的
二、使用场景
比如一个文件夹(容器对象)下可以包含多个文件(叶子对象),而这个文件夹又可以被其他文件夹包含,这样不断的递归下去,形成复杂的树形结构。并且有时为了方便调用,我们希望不管是对于其中的文件还是文件夹都可以进行相同的操作,以一致的方式来处理他们,这时可以使用组合模式。
也就是:
- 当想表达对象的部分-整体的层次结构时。
- 用户希望忽略组合对象与单个对象的不同,可以统一地使用组合结构中的所有对象时。
三、举例:
透明式的组合模式
在Component中声明所有来管理子对象的方法,其中包括add,remove等。这样实现Component接口的所有子类都具备了Add和Remove方法。
这样做的好处在于叶节点和枝节点对于外界没有区别,它们具备完全一致的接口。弊端在于虽然客户端对叶节点和枝节点是一致的,但叶节点并不具备add和remove的功 能,因而对它们的实现是没有意义的
//1.首先定义将要被组合的抽象部件(Component),用于访问和管理Component的子部件
abstract class AbstractFile{
protected String name;
public AbstractFile(String name){
this.name=name;
}
//增加一个子部件
public abstract void add(AbstractFile file);
//移除一个子部件
public abstract void remove(AbstractFile file);
//浏览当前部件
public abstract void display();
}
//2.定义一类叶子对象(Leaf)
class TextFile extends AbstractFile{
public TextFile(String name) {
super(name);
}
/*
* 由于叶子节点没有子节点,所以add和remove方法对它来说没有意义,但它继承自AbstractFile,
* 这样做可以消除叶节点和枝节点对象在抽象层次的区别,它们具备完全一致的接口。
*/
@Override
public void add(AbstractFile file) {
System.out.println("不能添加一个部件到叶子中");
}
// 实现它没有意义,只是提供了一个一致的调用接口
@Override
public void remove(AbstractFile file) {
System.out.println("不能从叶子中移除一个部件");
}
@Override
public void display() {
System.out.println("这是文本文件,文件名:" + super.name);
}
}
//3.定义一类容器对象(枝节点Composite)
class FileFolder extends AbstractFile{
//一个子对象集合,用来存储其下属的枝节点和叶节点
private List<AbstractFile>children=new ArrayList<>();
public FileFolder(String name) {
super(name);
}
@Override
public void add(AbstractFile file) {
children.add(file);
}
@Override
public void remove(AbstractFile file) {
children.remove(file);
}
@Override
public void display() {
System.out.println(super.name+"文件夹中包含:");
for(AbstractFile file:children){
System.out.print(" ");
file.display();
}
}
}
//测试:
public class 组合模式 {
public static void main(String[] args) {
/*
* 我们定义一个hwj文件夹,它包含h文件和wj子文件夹,而wj子文件夹中又包含w和j文件
*/
//首先,定义一个hwj文件夹
AbstractFile hwjFolder=new FileFolder("hwj");
//定义一个h文件和wj子文件夹,并把它们加入到hwj文件夹中
AbstractFile hFile=new TextFile("h");
AbstractFile wjFolder=new FileFolder("wj");
hwjFolder.add(hFile);
hwjFolder.add(wjFolder);
//定义一个w文件和j文件,并把它们加入到wj子文件夹中
AbstractFile wFile=new TextFile("w");
AbstractFile jFile=new TextFile("j");
wjFolder.add(wFile);
wjFolder.add(jFile);
//显示hwj文件夹中包含的所有文件
hwjFolder.display();
}
}
测试结果
hwj文件夹中包含:
这是文本文件,文件名:h
wj文件夹中包含:
这是文本文件,文件名:w
这是文本文件,文件名:j
安全式的组合模式
在Component中不去声明add和remove方法,那么子类的Leaf就不需要实现它不需要的方法,而是在Composit声明所有用来管理子类对象的方法。。
这样做虽然使得叶节点无需在实现自己不需要的方法,但是对于客户端来说,必须对叶节点和枝节点进行判定,为客户端的使用带来不便。
//1.首先定义将要被组合的抽象部件(Component),用于访问和管理Component的子部件
abstract class AbstractFile{
protected String name;
public AbstractFile(String name){
this.name=name;
}
//浏览当前部件
public abstract void display();
}
//2.定义一类叶子对象(Leaf)
class TextFile extends AbstractFile{
public TextFile(String name) {
super(name);
}
@Override
public void display() {
System.out.println("这是文本文件,文件名:" + super.name);
}
}
//3.定义一类容器对象(枝节点Composite),用来存储部件,实现在Component接口中对子部件有关的操作
class FileFolder extends AbstractFile{
private List<AbstractFile>children=new ArrayList<>();
public FileFolder(String name) {
super(name);
}
public void add(AbstractFile file) {
children.add(file);
}
public void remove(AbstractFile file) {
children.remove(file);
}
@Override
public void display() {
System.out.println(super.name+"文件夹中包含:");
for(AbstractFile file:children){
System.out.print(" ");
file.display();
}
}
}
注:《设计模式》一书中提倡:相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。