计算机的文件系统中,有“文件夹”的概念。文件夹中既可以放入文件,也可以放入其他文件夹。在子文件夹中依然可以放入文件或者文件夹。所以文件夹形成了一种容器结构、递归结构。
所以在目录条目中,文件夹和文件被当作时一个同一个对象看待。有的时候,将文件夹和文件都作为目录条目看待,将容器和内容作为某一种对象看待,可以帮助我们更便捷地处理问题。形成了容器结构、递归结构。
Composite模式就是用于创造出这样结构的模式。能够使容器和内容具有一致性,创造出递归结构的模式就是Composite模式。

代码实例:

名字 说明
Entry 抽象类,用于实现File类和Directory类的一致性
File 表示文件的类
Directory 表示文件夹的类
FileTreatementException 表示向文件中增加Entry时发生的异常的类
Main 测试程序行为的类

Entry类:

Entry是一个表示目录条目的抽象类。File类和Directory类是它的子类。可以通过getName方法获取其名字,通过getSize方法获得大小。PrintList方法在运行时会根据传递的参数类型选择并执行合适的printList方法。

  1. public abstract class Entry {
  2. public abstract String getName(); // 获取名字
  3. public abstract int getSize(); // 获取大小
  4. public Entry add(Entry entry) throws FileTreatMentException{ //加入目录条目
  5. throw new FileTreatMentException();
  6. }
  7. public void printList(){
  8. printList(""); // 显示目录条目
  9. }
  10. public abstract void printList(String prefix);
  11. public String toString(){
  12. return getName()+"("+getSize()+")";
  13. }
  14. }

File类:

File类代表文件,是Entry类的子类。代码中的this在和字符串直接相加的时候,等价于this.toString()。

  1. public class File extends Entry{
  2. private String name;
  3. private int size;
  4. public File(String name, int size) {
  5. this.name = name;
  6. this.size = size;
  7. }
  8. @Override
  9. public String getName() {
  10. return name;
  11. }
  12. @Override
  13. public int getSize() {
  14. return size;
  15. }
  16. @Override
  17. public void printList(String prefix) {
  18. System.out.println(prefix+"/"+this);
  19. }
  20. }

Directory类:

表示文件夹的类,同样也是Entry类的子类。
getSize是需要注意的地方,在变量size中加上了entry的大小,但entry可能是File类的实例,也可能是Directory类的实例。不过无论它是哪个类的实例,都可以通过getSize方法得到它的大小。这就是Composite模式的特征——“容器和内容的一致性”——的表现。Directory和File都是Entry类的子类。因此可以放心地调用getSize方法。即便将来编写了其他Entry类的子类,它也会实现getSize方法,因此之前已经写完的类不需要再进行修改。getSize方法会循环的调用子文件夹的这个方法,方法的递归调用与Composite模式的结构是相对应的。
同理,printList方法也是循环调用的。

  1. public class Directory extends Entry{
  2. private String name;
  3. private ArrayList directory = new ArrayList(); // 文件夹的目录条目的集合
  4. public Directory(String name) {
  5. this.name = name;
  6. }
  7. @Override
  8. public String getName() {
  9. return name;
  10. }
  11. @Override
  12. public int getSize() {
  13. int size=0;
  14. Iterator iterator = directory.iterator();
  15. while (iterator.hasNext()) {
  16. Entry next = (Entry)iterator.next();
  17. size+=next.getSize();
  18. }
  19. return size;
  20. }
  21. public Entry add(Entry entry){
  22. directory.add(entry);
  23. return this;
  24. }
  25. @Override
  26. public void printList(String prefix) {
  27. System.out.println(prefix+"/"+this);
  28. Iterator iterator = directory.iterator();
  29. while (iterator.hasNext()) {
  30. Entry next = (Entry) iterator.next();
  31. next.printList(prefix+"/"+name);
  32. }
  33. }
  34. }

FileTreatMentException:

对文件调用add方法时抛出的异常。

  1. public class FileTreatMentException extends RuntimeException{
  2. public FileTreatMentException(){}
  3. public FileTreatMentException(String msg){
  4. super(msg);
  5. }
  6. }

Main:

  1. public class Main {
  2. public static void main(String[] args) {
  3. try {
  4. System.out.println("开始创建root文件夹");
  5. Directory root = new Directory("root");
  6. Directory bin = new Directory("bin");
  7. Directory tmp = new Directory("tmp");
  8. Directory usr = new Directory("usr");
  9. root.add(bin);
  10. root.add(tmp);
  11. root.add(usr);
  12. bin.add(new File("vi",10000));
  13. bin.add(new File("latex",20000));
  14. root.printList();
  15. System.out.println("");
  16. System.out.println("开始创建user文件夹");
  17. Directory yuki = new Directory("yuki");
  18. Directory hanako = new Directory("hanako");
  19. Directory tomura = new Directory("tomura");
  20. usr.add(yuki);
  21. usr.add(hanako);
  22. usr.add(tomura);
  23. yuki.add(new File("diary.html",100));
  24. yuki.add(new File("composite.java",200));
  25. hanako.add(new File("memo.txt",300));
  26. tomura.add(new File("game.doc",400));
  27. tomura.add(new File("junk.mail",500));
  28. root.printList();
  29. } catch (FileTreatMentException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }

Composite模式中的登场角色:

Leaf(树种):

表示“内容”的角色。在该角色中不能放入其他对象。在实例代码中,由File类表示这一对象。

Composite(复合物):

表示容器的角色,可以在其中放入Leaf角色和Composite角色。Directory类扮演这一角色。

Component:

使Leaf角色和Composition角色具有一致性的角色。Component角色是Leaf和Composite角色的父类。

Cilent:

使用Composite模式的角色。

拓展思路:

多个和单个的一致性:

使用Composite模式可以使容器和内容具有一致性,也可以被称做是多个和单个的一致性,即将多个对象结合在一起,当作一个对象进行处理。
例如,Test1是用来测试输入数据来自键盘输入时的程序行为,Test2时用来测试输入数据来自文件时的…,Test3是用来测试输入数据来自网络时的…。Composite就可以将这三种测试结合在一起作为“输入测试”,或时将其他几个测试结合在一起作为“输出测试”,甚至可以将“输入”、“输出”结果合在一起。

Add方法应该放在哪里:

在实例程序中,Entry类中定义了add方法,所做的处理是抛出异常,这是因为能使用add方法的只能是Directory类。add方法有多种定义的位置;

  • 定义在Entry类中,报错

将add方法定义在Entry类中,让其报错,这是示例代码的做法,能使用add方法的只有Directory类,只需要重写add方法,根据需求实现其处理。
File类会继承Entry类的add方法,虽然也可以调用它的add方法,不过会抛出异常。

  • 定义在Entry类中,但什么都不做

也可以将add方法定义在Entry类中,但什么处理都不做。

  • 声明在Entry类中,但不实现

也可以在Entry类中声明add抽象方法。如果子类需要add抽象方法就根据需求实现该方法,如果不需要add方法,则可以简单的报错。该方法的优点是所有子类必须都实现add方法,不需要add方法时的处理也可以交给子类自己去做决定。不过,最下层的类必须定义本来完全不需要的add方法,所以定义在Entry中可以避免这一操作;

  • 只定义在Directory类中

因为只有Directory类可以使用add方法,所以可以不在Entry类中定义add方法,而是只将其定义在Directory类中。不过,使用这种方法时,如果要向Entry类型的变量中add时,需要先将它们一个一个地类型转换为Directory类型。

递归结构:

通常来说,树结构的数据结构都使用Composite模式。

相关的设计模式:

Command模式:

使用Command模式编写宏命令时使用了Composite模式。

Visitor模式:

可以使用Vistor模式访问Composite模式。

Decorator模式:

Compositemos1通过Component角色使容器和内容具有一致性;
Decorator模式使装饰框和内容具有一致性。