在面向对象的系统中,我们经常会遇到一类具有“容器”特征的对象,即它们在充当对象的同时,又是其他对象的容器。
举例:在操作系统中,文件的概念很广泛,其中文件可以是普通文件,也可以是目录(在Unix中,设备也是文件),目录中可以存放文件。如下图:
图1
File :抽象的文件类
PhysicalFile :普通文件
Directory :目录
// File.java
package com.pnft.patterns.composite;
public abstract class File
{
public abstract void process();
}
// PhysicalFile.java
package com.pnft.patterns.composite;
public class PhysicalFile extends File
{
@Override
public void process()
{
System.out.println("Method process() in PhysicalFile...");
}
}
// Directory.java
package com.pnft.patterns.composite;
import java.util.ArrayList;
public class Directory extends File
{
private ArrayList<File> arrayList;
@Override
public void process()
{
System.out.println("Method process() in Directory...");
}
public Directory()
{
arrayList = new ArrayList<File>();
}
public ArrayList<File> getFiles()
{
return arrayList;
}
public void add(File file)
{
arrayList.add(file);
}
public void remove(File file)
{
if(file != null)
{
arrayList.remove(file);
}
}
}
// PatternClient.java
package com.pnft.patterns.composite;
import java.util.ArrayList;
import java.util.Iterator;
public class PatternClient
{
private void recursiveProcess(ArrayList<File> arrayList)
{
if (arrayList != null)
{
for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)
{
File f = iter.next();
if(f instanceof Directory)
{
((File)f).process();
recursiveProcess(((Directory) f).getFiles());
}
else
{
f.process();
}
}
}
}
public static void main(String[] args)
{
PatternClient pc = new PatternClient();
File f = new Directory();
f.process();
File f1 = new Directory();
File f2 = new PhysicalFile();
((Directory)f1).add(f2);
((Directory)f).add(f1);
File f3 = new Directory();
File f4 = new Directory();
File f5 = new Directory();
File f6 = new PhysicalFile();
((Directory)f5).add(f6);
((Directory)f4).add(f5);
((Directory)f3).add(f4);
((Directory)f).add(f3);
ArrayList<File> arrayList = ((Directory)f).getFiles();
pc.recursiveProcess(arrayList);
}
}
上述程序运行结果:
Method process() in Directory...
Method process() in Directory...
Method process() in PhysicalFile...
Method process() in Directory...
Method process() in Directory...
Method process() in Directory...
Method process() in PhysicalFile...
上面程序存在的问题:
在容器类Directory中存在一个getFiles方法,而该方法的存在直接导致了客户程序PatternClient中的recursiveProcess方法的存在,这说明Directory类和客户程序PatternClient的耦合太紧(另外还有,在PatternClient中要判断一个对象是Directory还是PhysicalFile,以便采取不同的处理方式等等,都说明了耦合过紧这一情况),而且recursiveProcess方法是递归的,这于程序的维护不甚合适。因此,问题的根源在于:客户代码过多地依赖对象容器的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户端嗲吗的频繁变化,从而带来了代码的维护性和扩展性等方面的弊端。
Composite设计模式就是将“客户代码与复杂的对象昂容器结构”解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象(PhysicalFile)一样来处理复杂的对象容器(Directory)。
Composite设计模式的意图就是,将对象组合成属性结构以表示“部分-整体”的层次结构。它使得客户对单个对象和组合对象的使用具有一致性。-GoF
下面我们用Composite设计模式来解决上面的问题,具体代码如下,其中被注解的部分是未采用Composite设计模式之前的代码:
// File.java
package com.pnft.patterns.composite;
public abstract class File
{
public abstract void process();
public abstract void add(File file); // 将add方法抽象出来
public abstract void remove(File file); // 将remove方法抽象出来
}
// PhysicalFile.java
package com.pnft.patterns.composite;
public class PhysicalFile extends File
{
@Override
public void process()
{
System.out.println("Method process() in PhysicalFile...");
}
@Override
public void add(File file)
{
// add方法在本类中完全没有必要,但是为了将Directory类中的add方法抽取到File中,
// 因此必须在本类override这个add方法,这是为了在客户程序中不需要区分到底是
// Directory类还是PhysicalFile类,从而减少客户程序的依赖。
// 这个方法可以为空,或者抛出一个自定义的异常。
// 不很完美,因为在本类中被迫增加了这个方法。
}
@Override
public void remove(File file)
{
// remove方法在本类中完全没有必要,但是为了将Directory类中的remove方法抽取到File中,
// 因此必须在本类override这个add方法,这是为了在客户程序中不需要区分到底是
// Directory类还是PhysicalFile类,从而减少客户程序的依赖。
// 这个方法可以为空,或者抛出一个自定义的异常。
// 不很完美,因为在本类中被迫增加了这个方法。
}
}
// Directory.java
package com.pnft.patterns.composite;
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends File
{
private ArrayList<File> arrayList;
@Override
public void process()
{
System.out.println("Method process() in Directory...");
// 下面if块是按照Composite设计模式的要求新增加的代码
if(arrayList != null)
{
for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)
{
File f = (File)iter.next();
f.process();
}
}
}
public Directory()
{
arrayList = new ArrayList<File>();
}
// public ArrayList<File> getFiles() // 注释掉
// {
// return arrayList;
// }
public void add(File file)
{
arrayList.add(file);
}
public void remove(File file)
{
if(file != null)
{
arrayList.remove(file);
}
}
}
// PatternClient.java
package com.pnft.patterns.composite;
//import java.util.ArrayList; // 注释掉
//import java.util.Iterator;
public class PatternClient
{
// private void recursiveProcess(ArrayList<File> arrayList)
// {
// if (arrayList != null)
// {
// for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)
// {
// File f = iter.next();
// if(f instanceof Directory)
// {
// ((File)f).process();
// recursiveProcess(((Directory) f).getFiles());
// }
// else
// {
// f.process();
// }
// }
// }
// }
public static void main(String[] args)
{
// PatternClient pc = new PatternClient();
File f = new Directory();
// f.process();
File f1 = new Directory();
File f2 = new PhysicalFile();
// ((Directory)f1).add(f2); // 注释掉这2行代码,
// ((Directory)f).add(f1); // 用下面两行代码代替
f1.add(f2);
f.add(f1);
File f3 = new Directory();
File f4 = new Directory();
File f5 = new Directory();
File f6 = new PhysicalFile();
// ((Directory)f5).add(f6); // 注释掉这4行代码,
// ((Directory)f4).add(f5);
// ((Directory)f3).add(f4);
// ((Directory)f).add(f3); // 用下面4行代码代替
f5.add(f6);
f4.add(f5);
f3.add(f4);
f.add(f3);
f.process();
// ArrayList<File> arrayList = ((Directory)f).getFiles(); // 注释掉
// pc.recursiveProcess(arrayList);
}
}
各对象之间的关系:
图2
从上面程序看,PatternClient已经被简化了许多,而且仅对抽象类File有依赖(创建对象除外,创建对象可以用诸如Simple Factory设计模式来解决,在此不赘述)。上面程序运行结果:
Method process() in Directory...
Method process() in Directory...
Method process() in PhysicalFile...
Method process() in Directory...
Method process() in Directory...
Method process() in Directory...
Method process() in PhysicalFile...
可以看到,该结果和前面程序的结果是一致的。前后两个程序的重要区别:
1. 递归并没有消除,后者将递归移动到了Directory类中的process方法,同时虽然是递归,但是非常自然,易于理解;而前者需要客户程序来处理递归,从而从代码维护的角度而言存在很大的差别。
2. 后者将add方法和remove方法抽象到了File类中,从而保证在客户程序中不需要进行转型(Type Casting)处理,从而大幅度降低了客户程序代码复杂程度和对外部依赖程序。这样做的一个glitch就是,在PhysicalFile类中被迫增加了add方法和remove方法。
下面是后者的UML类图:
图3
实际上严格的Composite静态结构图如下:
图4
同时一定要记住:
Leaf中的add方法和remove方法,必须为空(即不存在任何代码)或者只抛出异常即可。因为Leaf类是叶子类,不能作为容器,因此也就无从add或者remove了。
我们可以看到,图1的大致结构看起来和图2、图3没有太大的不同,它们之间的主要区别是:
1. 一些方法是否需要存在
2. 这些方法所处的位置
3. 方法内容不同
因此不能仅从静态结构图比较类似,就判定对应图1是实现的项目代码就符合Composite设计模式的。通过前面两个不同的Java项目的代码,可以非常清楚地看出这一点。