计算机的文件系统中,有“文件夹”的概念。文件夹中既可以放入文件,也可以放入其他文件夹。在子文件夹中依然可以放入文件或者文件夹。所以文件夹形成了一种容器结构、递归结构。
所以在目录条目中,文件夹和文件被当作时一个同一个对象看待。有的时候,将文件夹和文件都作为目录条目看待,将容器和内容作为某一种对象看待,可以帮助我们更便捷地处理问题。形成了容器结构、递归结构。
Composite模式就是用于创造出这样结构的模式。能够使容器和内容具有一致性,创造出递归结构的模式就是Composite模式。
代码实例:
| 名字 | 说明 |
|---|---|
| Entry | 抽象类,用于实现File类和Directory类的一致性 |
| File | 表示文件的类 |
| Directory | 表示文件夹的类 |
| FileTreatementException | 表示向文件中增加Entry时发生的异常的类 |
| Main | 测试程序行为的类 |
Entry类:
Entry是一个表示目录条目的抽象类。File类和Directory类是它的子类。可以通过getName方法获取其名字,通过getSize方法获得大小。PrintList方法在运行时会根据传递的参数类型选择并执行合适的printList方法。
public abstract class Entry {public abstract String getName(); // 获取名字public abstract int getSize(); // 获取大小public Entry add(Entry entry) throws FileTreatMentException{ //加入目录条目throw new FileTreatMentException();}public void printList(){printList(""); // 显示目录条目}public abstract void printList(String prefix);public String toString(){return getName()+"("+getSize()+")";}}
File类:
File类代表文件,是Entry类的子类。代码中的this在和字符串直接相加的时候,等价于this.toString()。
public class File extends Entry{private String name;private int size;public File(String name, int size) {this.name = name;this.size = size;}@Overridepublic String getName() {return name;}@Overridepublic int getSize() {return size;}@Overridepublic void printList(String prefix) {System.out.println(prefix+"/"+this);}}
Directory类:
表示文件夹的类,同样也是Entry类的子类。
getSize是需要注意的地方,在变量size中加上了entry的大小,但entry可能是File类的实例,也可能是Directory类的实例。不过无论它是哪个类的实例,都可以通过getSize方法得到它的大小。这就是Composite模式的特征——“容器和内容的一致性”——的表现。Directory和File都是Entry类的子类。因此可以放心地调用getSize方法。即便将来编写了其他Entry类的子类,它也会实现getSize方法,因此之前已经写完的类不需要再进行修改。getSize方法会循环的调用子文件夹的这个方法,方法的递归调用与Composite模式的结构是相对应的。
同理,printList方法也是循环调用的。
public class Directory extends Entry{private String name;private ArrayList directory = new ArrayList(); // 文件夹的目录条目的集合public Directory(String name) {this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic int getSize() {int size=0;Iterator iterator = directory.iterator();while (iterator.hasNext()) {Entry next = (Entry)iterator.next();size+=next.getSize();}return size;}public Entry add(Entry entry){directory.add(entry);return this;}@Overridepublic void printList(String prefix) {System.out.println(prefix+"/"+this);Iterator iterator = directory.iterator();while (iterator.hasNext()) {Entry next = (Entry) iterator.next();next.printList(prefix+"/"+name);}}}
FileTreatMentException:
对文件调用add方法时抛出的异常。
public class FileTreatMentException extends RuntimeException{public FileTreatMentException(){}public FileTreatMentException(String msg){super(msg);}}
Main:
public class Main {public static void main(String[] args) {try {System.out.println("开始创建root文件夹");Directory root = new Directory("root");Directory bin = new Directory("bin");Directory tmp = new Directory("tmp");Directory usr = new Directory("usr");root.add(bin);root.add(tmp);root.add(usr);bin.add(new File("vi",10000));bin.add(new File("latex",20000));root.printList();System.out.println("");System.out.println("开始创建user文件夹");Directory yuki = new Directory("yuki");Directory hanako = new Directory("hanako");Directory tomura = new Directory("tomura");usr.add(yuki);usr.add(hanako);usr.add(tomura);yuki.add(new File("diary.html",100));yuki.add(new File("composite.java",200));hanako.add(new File("memo.txt",300));tomura.add(new File("game.doc",400));tomura.add(new File("junk.mail",500));root.printList();} catch (FileTreatMentException e) {e.printStackTrace();}}}
Composite模式中的登场角色:
Leaf(树种):
表示“内容”的角色。在该角色中不能放入其他对象。在实例代码中,由File类表示这一对象。
Composite(复合物):
表示容器的角色,可以在其中放入Leaf角色和Composite角色。Directory类扮演这一角色。
Component:
使Leaf角色和Composition角色具有一致性的角色。Component角色是Leaf和Composite角色的父类。
Cilent:
拓展思路:
多个和单个的一致性:
使用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类型。
递归结构:
相关的设计模式:
Command模式:
使用Command模式编写宏命令时使用了Composite模式。
Visitor模式:
Decorator模式:
Compositemos1通过Component角色使容器和内容具有一致性;
Decorator模式使装饰框和内容具有一致性。
