DesignPattern之复合模式
1. Definition
组合模式允许将对象组合成树形结构来表现“整体 / 部分”层次结构,能让客户以==一致==的方式处理个别对象及对象组合(一视同仁)
2. 分析(Head First Design Pattern p358)
根据 Head First Design Pattern 第358页的uml图来分析:
类图当中有三个类,一个是Component(节点的统一接口),它的目的是为了统一节点的操作。
接下来的两个实现类,一个则是非叶子节点(Composite),它可以有子节点,子节点可以是Leaf,也可以是Composite。
另外一个则是叶子节点(Leaf),它不能含有子节点。
客户操作的Component,也就是说,无论是Leaf还是Composite,在客户看来其实都是Component。那如何让客户将Composite和Leaf一视同仁呢?很简单,只要让Component中同时包含Leaf和Composite的方法就可以了。
- 那现在问题又来了,有些方法是Leaf或者Composite独有的,但是Leaf或者Composite都实现了Component接口,必须实现全部方法,然而让Leaf实现Composite独有的方法并没有什么意义。如何解决这个问题呢?——> 解决的方案有好多种,说一种书上给出的方案:让这些方法抛出
UnsupportedOperationException
就可以了。
3. 例子
就拿电脑的文件系统来举例子:文件夹是一个文件,单个的文件,比如“.avi”, ”.txt”也是文件,所以可以让它们都实现一个MyFile
接口。它们之间的共性有好多,比如都可以进行删除操作。但是删除文件跟删除文件夹是有区别的:
- 删除文件:只需要删除当前文件就可以了
- 删除文件夹:必须要删除这个文件夹里面的所有文件和文件夹
那么组合模式的一致性(客户能一视同仁的地方)就体现在: 客户不需要知道当前操作的是单个文件还是文件夹,客户只知道要进行删除文件(这里的文件指的是接口MyFile
)操作就可以了
- 接口
MyFile
~~~java
package composite;
public interface MyFile {
public void delete();
public String getName();
public void createNewFile(String name);
// Only for folder
public void deleteFile(String name);
// only for folder
public MyFile getMyFile(int index);
public void show();
}
~~~
- 文件夹 Folder:
Folder中有一个集合,用来保存文件夹里面的单个文件及子文件夹
~~~java
package composite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Folder implements MyFile {
private String name;
private MyFile folder;
private List<MyFile> files;
public Folder(String name) {
this(name, null);
}
public Folder(String name, MyFile folder) {
this.name = name;
this.folder = folder;
this.files = new ArrayList<MyFile>();
}
@Override
public void delete() {
System.out.println("delete subfiles...\n");
for (Iterator<MyFile> i = this.files.iterator(); i.hasNext();) {
i.next();
i.remove();
}
System.out.println("finished deleting subfiles!\n");
System.out.println("---Deleted [" + this.name + "]---\n");
}
@Override
public String getName() {
return this.name;
}
@Override
public void createNewFile(String name) {
if (name.contains(".")) {
files.add(new File(name, this));
} else {
files.add(new Folder(name, this));
}
}
@Override
public void deleteFile(String name) {
for (MyFile file : this.files) {
if (file.getName().equals(name)) {
this.files.remove(file);
System.out.println("---Deleted [" + file.getName() + "]---\n");
break;
}
}
}
@Override
public MyFile getMyFile(int index) {
return this.files.get(index);
}
@Override
public void show() {
System.out.println("------" + this.name + "------");
for (MyFile file : this.files) {
file.show();
}
}
}
~~~
- 单个文件 File:
~~~java
package composite;
public class File implements MyFile {
private String name;
private MyFile folder;
public File(String name, MyFile folder) {
this.name = name;
this.folder = folder;
}
@Override
public void delete() {
this.folder.deleteFile(this.name);
}
@Override
public String getName() {
return name;
}
@Override
public void createNewFile(String name) {
throw new UnsupportedOperationException();
}
@Override
public void deleteFile(String name) {
throw new UnsupportedOperationException();
}
@Override
public MyFile getMyFile(int index) {
throw new UnsupportedOperationException();
}
@Override
public void show() {
System.out.println("[" + this.name + "]");
}
}
~~~
- 测试类:
~~~java
~~~package composite;
public class Demo {
public static void main(String[] args) {
//-----------root-------------------
MyFile root = new Folder("My Computer");
root.createNewFile("Disk C");
root.createNewFile("Disk D");
root.createNewFile("Disk E");
//---------Disk D--------------------
MyFile diskD = root.getMyFile(1);
diskD.createNewFile("Project");
diskD.createNewFile("Movies");
//--------project--------------------
MyFile project = diskD.getMyFile(0);
project.createNewFile("Test1.java");
project.createNewFile("Test2.java");
project.createNewFile("Test3.java");
//--------movies--------------------
MyFile movies = diskD.getMyFile(1);
movies.createNewFile("Matrix1.avi");
movies.createNewFile("Matrix2.avi");
// print all files
System.out.println("Before deletion:");
root.show();
// Now try to delete some files!
System.out.println();
project.delete();
movies.deleteFile("Matrix2.avi");
System.out.println("After deletion:");
root.show();
}
}
~~~
- 结果:
~~~java
Before deletion:
------My Computer------
------Disk C------
------Disk D------
------Project------
-[Test1.java]
-[Test2.java]
-[Test3.java]
------Movies------
-[Matrix1.avi]
-[Matrix2.avi]
------Disk E------
delete subfiles...
finished deleting subfiles!
---Deleted [Project]---
---Deleted [Matrix2.avi]---
After deletion:
------My Computer------
------Disk C------
------Disk D------
------Project------
------Movies------
-[Matrix1.avi]
------Disk E------
~~~
4. 思考
组合模式以牺牲单一责任原则来换取透明性(transparency)。透明性指:通过让组件的接口同时包含有一些管理Composite和Leaf的操作,客户就可以将两者一视同仁。也就是说,一个元素究竟是Composite和Leaf,对客户来说是透明的。
当然也可以采用另一种方向的设计,将接口一分为二,将责任区分开来放在不同的接口中。这样一来,设计上比较安全,但也因此失去了透明性,因为客户必须用条件语句和instanceof来判断不同类型的节点。
组合模式是一个经典的折中案例:尽管我们受到设计原则的指导,但是也需要观察某些原则对我们设计造成的影响,并故意做一些看似违反原则的设计来换取设计的平衡。