组合模式介绍
组合模式(Composite Pattern) 也称为部分整体模式(Part-Whole Pattern), 结构型设计模式之一,组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。生活中比较典型的一个例子就是组织结构的树状图。
例如一个公司的组织结构树状图,其中总公司下有行政部与研发部,并且总公司下属还有一个子公司,虽然子公司也含有行政部和研发部,但是从总公司的角度来看子公司就是一个独立的个体,与总公司所属的行政部和研发部平级,如果我们以一个嵌套盒子的形式来展示将会更加直观,比如我们将上述的树形结构转为一个嵌套盒子,总公司则是最外层的盒子,里面包含3个小盒子。分别表示总公司的行政部,研发部和子公司,而子公司内部又包含有研发部与行政部。
在这么一个结构中大家可以看到虽然总公司和子公司其本质不一样,但是它在我们的组织结构中是一样的,我们可以把它们看作一个抽象的公司,在组合模式中我们将这样拥有分支的结点称之为枝干构件,位于树状结构顶部的枝干结构比较特殊,我们称之为根结构件,因为整个树状图的始端,同样对于像行政部和研发部这样没有分支的结构,我们称之为叶子结构,这样的一个结构就是组合结构的雏形。
组合模式的定义
将对象组合成树形结构以表示“整体-部分”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式的使用场景
表示对象的部分-整体层次结构时。
从一个整体中能够独立出部分模块或功能的场景
组合模式的简单实现
对于程序员来说,介绍一些组织结构想必一定会很陌生,但是即便如此,我们也能在其他地方看到组合模式的影子,如果你使用过电脑,知道什么是操作系统,那么对文件和文件夹这两个概念一定不会陌生,什么是文件呢? 文件就是可以被具体程序执行的对象,那么什么是文件夹呢?文件夹就是可以存放文件和文件夹的对象
如此一看,操作系统的文件系统其实就是一种典型的组合模式的例子,在这里我们以此为例,看看一个简单的文件系统是如何构成的,首先声明一个Dir抽象类表示文件和文件夹。
public abstract class Dir{
//声明一个List成员变量存储文件夹下的所有元素
protected List<Dir> dirs=new ArrayList<>();
private String name;//当前文件或文件夹名
public Dir(String name){
this.name=name;
}
//添加一个文件或文件夹
//@param dir
public abstract void addDir(Dir dir);
//移除一个文件或文件夹
//@param dir
public abstract void rmDir(Dir dir);
//清空文件夹下所有的元素
public abstract void clear();
//输出文件夹目录结构
public abstract void print();
//获取文件夹下所有的文件或子文件夹
//@return 文件夹下所有的文件或子文件
public abstract List<Dir> getFiles();
//获取文件或文件夹名
//@return文件或文件夹名
public String getName(){
return name;
}
}
在该抽象类中我们定义了相关的抽象方法,大家可以看到这里用到的就是所谓的透明的组合模式,这里要实现的功能很简单,只是简单地打印一下目录结构,因此,声明一个成员变量来存储当前文件或文件夹的名称并提供对应的getter方法。接下来就是具体的文件夹类,该类中我们实现具体的文件夹方法逻辑:
//表示文件夹的类
public class Folder extends Dir{
public Folder(String name){
super(name);
}
@Override
public void addDir(Dir dir){
dirs.add(dir);
}
@Override
public void rmDir(Dir dir){
dirs.remove(dir);
}
@Override
public void clear(){
dirs.clear();
}
@Override
public void priint(){
System.out.print(getName()+ "(");
Iterator<Dir> iter=dirs.iterator();
while(iter.hasNext()){
Dir dir=iter.next();
dir.print();
if(iter,hasNext()){
System.out.print(",");
}
}
System.out.print(")");
}
@Override
public List<Dir> getFiles(){
return dirs;
}
}
像addDir,rmDir在这里的方法中实现逻辑都比较简单,这里主要就是print方法用来输出文件夹的目录结构,该方法逻辑也比较简单,首先输出自己的名字,也就是当前文件夹名,然后迭代遍历子元素,调用子元素的print方法输出其目录结构,如果遇到子元素还是个文件夹,那么递归遍历直至所有的输出元素均为文件为止,接下来看一下文件类的实现
//表示文件的类
public class File extends Dir{
public File(String name){
super(name);
}
@Override
public void addDir(Dir dir){
throw new UnsupportedOperationException("文件对象不支持该操作");
@Override
public void rmDir(Dir dir){
throw new UnsupportedOperationException("文件对象不支持该操作");
@Override
public void clear(){
throw new UnsupportedOperationException("文件对象不支持该操作");
@Override
public void print(){
throw new UnsupportedOperationException("文件对象不支持该操作");
@Override
public List<Dir> getFile(){
throw new UnsupportedOperationException("文件对象不支持该操作");
}
}
文件类相对于文件夹类来说既不支持添加也不支持删除,因为文件不能作为文件夹来使用,它本是文件系统中的最小分隔单位,因此,这里我们虽然实现了Dir中的一些添加,删除等操作方法,但是,其实逻辑只是抛出一个不支持异常,因为就文件本身来说就不支持对应的操作。最后,在一个客户类中构建目录结构并输出。
//客户类
public class Client{
public static void main(String[] args){
//构造一个目录对象表示C盘根目录
Dir diskC=new Folder("C");
//C盘根目录下有一个文件Text.txt
diskC.addDir(new File("Text.txt"));
//C盘目录下还有3个子目录Windows,PerfLog,Program File
//Window目录
Dir dirWin=new Folder("window");
//Window目录下有文件syudy.exe
dirWin.addDir(new File("study.exe"));
diskC.addDir(dirWin);
//PerfLogs目录
Dir dirPer=new Folder("PerfLogs");
//PerfLogs目录下有文件ljz.txt
dirPer.addDir(new File("ljz.txt"));
diskC.addDir(dirPre);
//Program File目录
Dir dirPro=new Folder("program File");
//program File目录下有文件ftp.txt
dirPro.addDir(new File("ftp.txt"));
diskC.addDir(dirPro);
//打印出文件结构
diskC.print();
}
}
输出结果
C (Text.txt, Window(study.exe), PerfLogs(ljz.txt), program File(ftp.txt) )
分析
这里我们以括号作为一个文件夹的内容范围,如上输出所示,C盘文件夹下有3个子文件夹Window, PerfLogs和program File,以及一个文件Text.txt,而且这3个子文件夹中还各自包含有子文件,一个典型的树状嵌套结构,这就是组合模式。