1.定义
组合模式属于结构型模式,也叫整体-部分模式,目的是将单个对象和组合对象使用相同的接口进行表示,使得客户端对单个对象和组合对象的使用具有一致性。
可以将对象类之间的引用看做一棵树,组合对象(容器对象)就是树干,而单个对象就是树的叶子节点。
通过对两者使用相同的抽象方式,在客户端看来,可以当做一种对象进行处理,不需要关心内部具体是什么,而对于树而言,无论是整体还是部分,都具有相同的操作方式。
可以类比操作系统的文件系统,所有的东西被抽象成了文件,无论是目录、文件、块设备,进程信息等等,通过统一的描述,使得不同的设备可以向外提供统一的接口。
如果按照传统的方式去描述这些对象,那不得不为每一种类型建立一种抽象,客户端不得不维护多种类型的对象,一个文件夹中可能包含另外的文件夹,也可能包含文件,甚至一种设备信息,如果将他们的公共部分进行抽象,各个类型只需要完成他们各自的行为即可。
通过公共的抽象,对客户端屏蔽了各个类型层次的差异,将客户端和对象解耦,并且可以任意的增加容器对象和叶子对象,符合开闭原则。
因为将所有层次的对象进行统一的抽象,看上去有些违反了单一职责原则和接口隔离,但是设计模式本身就不是强行套用概念,不同的设计模式都是为了更好的解决一类问题,组合模式适合处理一些具有统一行为的树形结构对象,简化客户单的维护成本
其中包含以下几种角色
- Component(抽象构件):一般是抽象类,为容器对象和叶子对象进行统一的抽象描述,该角色包含了所有子类共有的行为
- Leaf(叶子构件):表示没有子节点的对象,实现了抽象构件中叶子节点应有的行为
- Composite(容器构件):容器对象,通过内部维护一个集合,包含了它的所有子节点,子节点可以是叶子对象或者容器对象,容器对象实现了容器对象应有的行为
图片来源:百度图片
2.示例
组合模式本质上看就是一个不规则的树一直向下延伸,直到叶子节点,关键点是将树的所有不同层次的节点进行抽象
以文件夹和文件做一个简单的实现,定义一个目录和文件的公共抽象类,因为父类职责并不单一,所以所有方法默认抛出异常,不允许调用父类
/**
* @description: 抽象构件component
* @version: 1.0
*/
public abstract class AbstractFile {
private String name;
public AbstractFile(String name) {
this.name = name;
}
public void addFile(AbstractFile file){
throw new UnsupportedOperationException();
}
public void removeFile(AbstractFile file){
throw new UnsupportedOperationException();
}
public AbstractFile getFile(int i){
throw new UnsupportedOperationException();
}
//打印所有文件
public void list(){
throw new UnsupportedOperationException();
}
//打印树结构
public void tree(AbstractFile pNode,int grade){
throw new UnsupportedOperationException();
}
}
定义叶子构件,实现抽象构件,因为是文件类型,只实现自身的行为,比如list和tree
/**
* @description: 容器构件
* @version: 1.0
*/
public class File extends AbstractFile{
private String name;
public File(String name) {
super(name);
this.name = name;
}
@Override
public void list() {
System.out.println(name);
}
public void tree(AbstractFile pNode,int grade) {
String n = "";
for (int i = grade; i >=0; i--){
n += " ";
}
System.out.println(n + "+——" + name);
}
}
最后是容器构件,容器构件应维护一个子节点构件集合,既可以是文件也可以是文件夹
/**
* @description: 容器构件
* @version: 1.0
*/
public class Folder extends AbstractFile{
private String name;
private List<AbstractFile> abstractFiles = new ArrayList<>();
public Folder(String name) {
super(name);
this.name = name;
}
@Override
public void addFile(AbstractFile file) {
abstractFiles.add(file);
}
@Override
public void removeFile(AbstractFile file) {
abstractFiles.remove(file);
}
@Override
public AbstractFile getFile(int i) {
return abstractFiles.get(i);
}
@Override
public void list() {
for (Iterator<AbstractFile> it = abstractFiles.iterator();it.hasNext();){
AbstractFile next = it.next();
next.list();
}
}
@Override
public void tree(AbstractFile pNode,int grade) {
String n = "";
for (int i = grade; i >=0; i--){
n += " ";
}
System.out.println(n + "+——" + name);
++grade;
for (Iterator<AbstractFile> it = abstractFiles.iterator();it.hasNext();){
AbstractFile next = it.next();
next.tree(this,grade);
}
}
}
测试
/**
* @description: test
* @version: 1.0
*/
public class Test {
public static void main(String[] args) {
AbstractFile f0 = new File("f0");
AbstractFile f1 = new File("f1");
AbstractFile f2 = new File("f2");
AbstractFile f3 = new File("f3");
AbstractFile f4 = new File("f4");
AbstractFile f5 = new File("f5");
AbstractFile f6 = new File("f6");
AbstractFile f7 = new File("f7");
AbstractFile f8 = new File("f8");
AbstractFile f9 = new File("f9");
AbstractFile dir0 = new Folder("dir0");
AbstractFile dir1 = new Folder("dir1");
AbstractFile dir2 = new Folder("dir2");
//将f0-f4添加到目录0中
dir0.addFile(f0);
dir0.addFile(f1);
dir0.addFile(f2);
dir0.addFile(f3);
dir0.addFile(f4);
//将f5 f6添加到目录1中
dir1.addFile(f5);
dir1.addFile(f6);
//将f7 f7 f8添加到目录2中
dir2.addFile(f7);
dir2.addFile(f8);
dir2.addFile(f9);
//将目录2添加到目录1
dir1.addFile(dir2);
//再将目录1添加到目录0
dir0.addFile(dir1);
//dir0.list();
dir0.tree(dir0,0);
}
}
input:
+——dir0
+——f0
+——f1
+——f2
+——f3
+——f4
+——dir1
+——f5
+——f6
+——dir2
+——f7
+——f8
+——f9
3.总结
组合模式适用打的场景,在具有整体和部分层次结构中,希望通过一种方式忽略整体和部分的差异,允许客户端一致的对待它们,或者系统中需要一个树结构,能够分离出叶子对象和容器对象,类型不固定,并且允许扩展。
文中部分内容参考自:博客