Composite模式模式能够使容器与内容具有一致性,创造出递归结构。有时,与将文件夹和文件都作为目录条目看待一样,将容器和内容作为同一种东西看待,可以帮助我们方便地处理问题。在容器中既可以放入内容,也可以放入小容器,然后在那个小容器中,又可以放入更小的容器。这样,就形成了容器结构、递归结构。
示例程序类图如上图。
1 package bigjunoba.bjtu.composite; 2 3 public abstract class Entry { 4 public abstract String getName(); // 获取名字 5 public abstract int getSize(); // 获取大小 6 public Entry add(Entry entry) throws FileTreatmentException { // 加入目录条目 7 throw new FileTreatmentException(); 8 } 9 public void printList() { // 为一览加上前缀并显示目录条目一览 10 printList(""); 11 } 12 protected abstract void printList(String prefix); // 为一览加上前缀 13 public String toString() { // 显示代表类的文字 14 return getName() + " (" + getSize() + ")"; 15 } 16 }
Entry类是一个表示目录条目的抽象类。为了能将文件夹Directory和文件File统一起来,所以设计了Entry类。其中printList方法有两种形式,称为重载。其中带参数的printList方法可见性是protected,即只能被Entry类的子类调用。
1 package bigjunoba.bjtu.composite; 2 3 public class File extends Entry { 4 private String name; 5 private int size; 6 public File(String name, int size) { 7 this.name = name; 8 this.size = size; 9 } 10 public String getName() { 11 return name; 12 } 13 public int getSize() { 14 return size; 15 } 16 protected void printList(String prefix) { 17 System.out.println(prefix + "/" + this); 18 } 19 }
File类是表示文件的类,调用构造函数,就会根据传入的文件名和文件大小生成文件实例。其中的this表示父类中的toString方法。
1 package bigjunoba.bjtu.composite; 2 3 import java.util.Iterator; 4 import java.util.ArrayList; 5 6 public class Directory extends Entry { 7 private String name; // 文件夹的名字 8 private ArrayList<Entry> directory = new ArrayList<Entry>(); // 文件夹中目录条目的集合 9 public Directory(String name) { // 构造函数 10 this.name = name; 11 } 12 public String getName() { // 获取名字 13 return name; 14 } 15 16 public int getSize() { // 获取大小 17 int size = 0; 18 Iterator<Entry> it = directory.iterator(); 19 while (it.hasNext()) { 20 Entry entry = (Entry)it.next(); 21 size += entry.getSize(); 22 } 23 return size; 24 } 25 26 public Entry add(Entry entry) { // 增加目录条目 27 directory.add(entry); 28 return this; 29 } 30 31 protected void printList(String prefix) { // 显示目录条目一览 32 System.out.println(prefix + "/" + this); 33 Iterator<Entry> it = directory.iterator(); 34 while (it.hasNext()) { 35 Entry entry = (Entry)it.next(); 36 entry.printList(prefix + "/" + name); 37 } 38 } 39 }
Directory类是表示文件夹的类。这里用泛型集合来保存文件夹中的目录条目。getSize方法返回集合中所有元素的大小的总和。
这里注意size += entry.getSize();不管entry是哪个类的实例,都可以通过getSize方法得到它的大小。这就是该模式的特征--容器与内容一致性-的表现。
注意这里使用了递归,如果entry是Directory类的实例,调用 entry.getSize()时会将该文件夹下的所有目录条目的大小加起来,如果还有子文件夹,又会调用子文件夹的getSize方法。即getSize方法的递归调用与Composite模式的结构是相对应的。
add方法不会判断接收到的是文件夹还是文件,而是通过委托给ArrayList类来实现。printList方法也会递归调用,不多解释了。
1 package bigjunoba.bjtu.composite; 2 3 public class FileTreatmentException extends RuntimeException { 4 public FileTreatmentException() { 5 } 6 public FileTreatmentException(String msg) { 7 super(msg); 8 } 9 }
由于调用add方法抛出的异常类并非Java类库的自带异常类,所以要编写自己的异常类。
1 package bigjunoba.bjtu.composite; 2 3 public class Main { 4 public static void main(String[] args) { 5 try { 6 System.out.println("Making root entries..."); 7 Directory rootdir = new Directory("root"); 8 Directory bindir = new Directory("bin"); 9 Directory tmpdir = new Directory("tmp"); 10 Directory usrdir = new Directory("usr"); 11 rootdir.add(bindir); 12 rootdir.add(tmpdir); 13 rootdir.add(usrdir); 14 bindir.add(new File("vi", 10000)); 15 bindir.add(new File("latex", 20000)); 16 rootdir.printList(); 17 18 System.out.println(""); 19 System.out.println("Making user entries..."); 20 Directory yuki = new Directory("yuki"); 21 Directory hanako = new Directory("hanako"); 22 Directory tomura = new Directory("tomura"); 23 usrdir.add(yuki); 24 usrdir.add(hanako); 25 usrdir.add(tomura); 26 yuki.add(new File("diary.html", 100)); 27 yuki.add(new File("Composite.java", 200)); 28 hanako.add(new File("memo.tex", 300)); 29 tomura.add(new File("game.doc", 400)); 30 tomura.add(new File("junk.mail", 500)); 31 rootdir.printList(); 32 } catch (FileTreatmentException e) { 33 e.printStackTrace(); 34 } 35 } 36 }
Main类作为测试类,也不做过多解释。
Making root entries... /root (30000) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (0) Making user entries... /root (31500) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (1500) /root/usr/yuki (300) /root/usr/yuki/diary.html (100) /root/usr/yuki/Composite.java (200) /root/usr/hanako (300) /root/usr/hanako/memo.tex (300) /root/usr/tomura (900) /root/usr/tomura/game.doc (400) /root/usr/tomura/junk.mail (500)
测试结果如上图,一目了然。