组合模式:
将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
Component为组合中对象声明接口,在适当的情况下,实现所有类的共有接口的默认行为。
声明一个接口用于管理和访问Component的子部件。
package com.design.composite;
public interface Component {
public void add(Component compent);
public void remove(Component compent);
public void display(int i);
}
Leaf表示叶节点对象,没有子节点
package com.design.composite;
public class Leaf implements Component{
private String name ;
public Leaf(String name) {
this.setName(name);
}
@Override
//叶节点没有子节点,实现add和remove没有意义,但是这样可以消除叶节点和枝节点的层次差别,使他们具备完全一致的接口
public void add(Component compent) {
throw new UnsupportedOperationException();
}
@Override
public void remove(Component compent) {
throw new UnsupportedOperationException();
}
@Override
public void display(int i) {
for(int m=0;m<i;m++){
System.out.print("-");
}
System.out.println(this.getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Composite表示枝节点,用来储存子部件,在component接口中实现与子部件的有关的操作:
package com.design.composite;
import java.util.ArrayList;
import java.util.List;
public class Composite implements Component {
private String name ;
public Composite(String name) {
this.setName(name);
}
//一个子对象集合用来存储其下的枝节点和叶节点
private List<Component> children = new ArrayList<Component>();
@Override
public void add(Component compent) {
children.add(compent);
}
@Override
public void remove(Component compent) {
children.remove(compent);
}
@Override
public void display(int i) {
//显示枝节点名称
for(int m=0;m<i;m++){
System.out.print("-");
}
System.out.println(this.getName());
++i;
//对子节点进行遍历
for (Component compent : children) {
compent.display(i);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
客户端:
package com.design.composite;
public class Test {
public static void main(String[] args) {
Component root = new Composite("总公司");
root.add(new Leaf("北京总部"));
Composite company = new Composite("海外分公司");
Composite department = new Composite("上海研发中心");
Composite club = new Composite("研发俱乐部");
root.add(company);
root.add(department);
department.add(club);
company.add(new Leaf("人力资源部"));
company.add(new Leaf("财务部"));
department.add(new Leaf("财务部"));
club.add(new Leaf("羽毛球俱乐部"));
Leaf leaf = new Leaf("足球俱乐部");
club.add(leaf);
club.add(new Leaf("电竞俱乐部"));
club.remove(leaf);
root.display(2);
}
}
结果为:
叶节点也实现了和枝节点相同的add及remove接口,叫做透明模式。也就是在component接口中声明所有的用来管理子对象的方法。这样实现component接口的所有子类对于外界没有区别,具备完全一致的行为接口。但是问题也很明显,叶节点本身实现的管理子节点的方法没有意义。
如果叶节点中不实现管理子节点的方法,就需要在component接口中不去声明add和remove方法,在枝节点composite中声明用来管理子类对象的方法,叫做安全模式。但是缺点是枝节点和叶节点不具备相同的接口,客户端调用的时候需要进行相应的判断。
当需求中是体现部分和整体层次的结构时,并且希望忽略组合对象和单个对象的不同,统一的使用组合结构中的所有对象时,应该考虑使用组合模式。
一个树结构的例子:文件系统。
首先,文件系统中,叶子节点是文件,非叶子节点是文件夹,所以Leaf和Composite实现类就是文件和文件夹。对于Component接口,其实也很简单,就是提取文件和文件夹的共性就可以了。
很显然,二者的共性有很多,比如都可以进行复制、剪切、删除、重命名等操作。但是不同的是,对于文件和文件夹的这些操作是有细微的区别的,最明显的就是删除操作,如果是文件,那么我们只需要删除当前文件即可,而如果是文件夹,则需要删除文件夹下的所有文件以及文件夹,然后再删除该文件夹。
那么定义当中的一致性就体现在,我们的客户端不需要知道当前操作的是文件还是文件夹,它只知道它要进行删除操作,而我们去针对文件类别的不同去进行相应的处理。
下面我们来模拟一下组合模式,采用文件系统。
首先,我们先给出一个接口,它相当于Component接口,定义了文件与文件夹的公共行为:
package com.composite;
//文件系统中的节点接口
public interface IFile {
//下面两个方法,相当于类图中operation方法
void delete();
String getName();
/* 以上为公共行为,以下为文件夹才有的行为 */
//创建新文件,相当于add方法
void createNewFile(String name);
//相当于remove方法
void deleteFile(String name);
//相当于GetChild方法
IFile getIFile(int index);
}
类图中的operation方法是一个宏观定义,它代表的意思是叶子节点和非叶子节点的公共行为,并不是说只有一个operation方法,在此给出两个共有行为作为代表,即删除操作和获取文件名称的操作。
非叶子节点,即文件夹的实现类:
package com.composite;
import java.util.ArrayList;
import java.util.List;
//文件夹
public class Folder implements IFile{
private String name;
private IFile folder;
private List<IFile> files;
public Folder(String name) {
this(name, null);
}
public Folder(String name,IFile folder) {
super();
this.name = name;
this.folder = folder;
files = new ArrayList<IFile>();
}
public String getName() {
return name;
}
//与File的删除方法不同,先删除下面的文件以及文件夹后再删除自己
public void delete() {
List<IFile> copy = new ArrayList<IFile>(files);
System.out.println("------------删除子文件-------------");
for (IFile file : copy) {
file.delete();
}
System.out.println("----------删除子文件结束-------------");
if (folder != null) {
folder.deleteFile(name);
}
System.out.println("---删除[" + name + "]---");
}
public void createNewFile(String name) {
if (name.contains(".")) {
files.add(new File(name,this));
}else {
files.add(new Folder(name,this));
}
}
public void deleteFile(String name) {
for (IFile file : files) {
if (file.getName().equals(name)) {
files.remove(file);
break;
}
}
}
public IFile getIFile(int index) {
return files.get(index);
}
}
这里面最主要的地方在于它有一个List<IFile>属性,这个属性是树结构的关键点,当我们删除一个文件夹时,即delete方法,我们会首先删除该文件夹下面的所有文件以及文件夹,这与我们平时使用的windows操作系统的文件操作是一致的。
接口中的三个方法,createNewFile、deleteFile和getIFile,分别对应于类图当中的add、remove以及getChild方法,只不过为了更加形象,修改了方法名称。
下面我们看叶子节点的实现,即文件类。
package com.composite;
//文件
public class File implements IFile{
private String name;
private IFile folder;
public File(String name,IFile folder) {
super();
this.name = name;
this.folder = folder;
}
public String getName() {
return name;
}
public void delete() {
folder.deleteFile(name);
System.out.println("---删除[" + name + "]---");
}
//文件不支持创建新文件
public void createNewFile(String name) {
throw new UnsupportedOperationException();
}
//文件不支持删除文件
public void deleteFile(String name) {
throw new UnsupportedOperationException();
}
//文件不支持获取下面的文件列表
public IFile getIFile(int index) {
throw new UnsupportedOperationException();
}
}
文件类中的delete方法与文件夹中的不同,一个文件的删除操作,只需要删除它自己即可。而且,下面的三个方法这里全部抛出了不支持的操作的异常,这也是与我们传统意义上的文件操作是一致的,一个文件当然不能在该文件下进行创新新文件、删除文件以及获取某个文件的操作。
当然,你也可以直接将三个方法放空,或者返回null值,不过这样的方式不易于以后进行调试。
模拟创建一个简单的文件系统,然后在上面进行删除操作。
package com.composite;
public class Main {
public static void main(String[] args) {
IFile root = new Folder("我的电脑");
root.createNewFile("C盘");
root.createNewFile("D盘");
root.createNewFile("E盘");
IFile D = root.getIFile(1);
D.createNewFile("project");
D.createNewFile("电影");
IFile project = D.getIFile(0);
project.createNewFile("test1.java");
project.createNewFile("test2.java");
project.createNewFile("test3.java");
IFile movie = D.getIFile(1);
movie.createNewFile("致青春.avi");
movie.createNewFile("速度与激情6.avi");
/* 以上为当前文件系统的情况,下面我们尝试删除文件和文件夹 */
display(null, root);
System.out.println();
project.delete();
movie.getIFile(1).delete();
System.out.println();
display(null, root);
}
//打印文件系统
public static void display(String prefix,IFile iFile){
if (prefix == null) {
prefix = "";
}
System.out.println(prefix + iFile.getName());
if(iFile instanceof Folder){
for (int i = 0; ; i++) {
try {
if (iFile.getIFile(i) != null) {
display(prefix + "--", iFile.getIFile(i));
}
} catch (Exception e) {
break;
}
}
}
}
}
我们首先模拟了一个简单的文件系统,有C/D/E盘,然后又在D盘下建立了两个文件夹以及一些文件,接下来我们使用统一的操作接口去操作文件和文件夹,进行删除操作。
在删除的前后,分别打印了一遍当前的文件系统,结果如下。
可以看到,我们成功删除了[project]文件夹和[速度与激情6.avi]文件,在删除[project]文件夹时,首先删除了其文件夹下面的三个java文件。
所以结合组合模式的定义,在上面的例子中,我们做了下面两件事,正好是组合模式定义中提到的。
- 使用组合模式,描述了一个文件系统的树结构;
- 在组合模式下,我们给客户端提供了统一的删除操作。当然,我们还可以提供统一的复制,剪切,查看文件属性等等操作,只不过作为例子,我们只列出了删除操作。
上面的例子当中,我们的叶子节点类(File)中,有三个不支持的方法,而之所以出现这样的情况,是因为我们在IFile接口中,提供的是宽接口,这样做的目的是为了对客户端保持透明,然而相应的却带来了不安全性。
所以有时候我们为了安全性,会相应的牺牲透明性,把IFile接口中叶子节点不支持的三个行为全部删掉,由此可见,在组合模式中,安全性和透明性是互相矛盾的,这是由于叶子节点和非叶子节点行为的不一致以及需要提供一个一致的行为接口所造成的,是不可调和的矛盾。
针对这种情况,我们只能做出相应的取舍,如果我们使用非透明且相对安全的方式去实现上面的例子,那么我们的客户端调用时,会经常出现下面这样的代码。
IFile movie = D.getIFile(1);
if (movie instanceof Folder) {
Folder folder = (Folder) movie;
//下面使用folder进行文件夹独有的操作
}
出现上面代码的原因很明显,这是由于我们IFile接口不再提供Folder的行为所造成的。所以使用非透明的组合模式,会相应的增加客户端操作的复杂性。
大部分情况下,我们应当优先考虑透明的策略。
最后总结一下组合模式的应用场景,其实就是定义当中所提到的两点:
1、如果你想表示“部分整体”的层次结构,可以使用组合模式。
2、如果你想让客户端可以忽略复杂的层次结构,使用统一的方式去操作层次结构中的所有对象,也可以使用组合模式。
共同学习,写下你的评论
评论加载中...
作者其他优质文章