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 ArrayListdirectory = new ArrayList (); // 文件夹中目录条目的集合 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 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 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)
测试结果如上图,一目了然。