组合模式,用于创造出递归结构,能够使容器和内容具有一致性
比如文件系统,文件目录里里可以放入文件,也可以放入子文件目录,同理子文件目录又可以放入文件,也可以放入子文件目录,这个文件目录就形成了一种容器结构,递归结构;虽然文件目录和文件是不同类型的对象,但是它们都可以放入到文件目录中,那么文件目录和文件可以统称为目录条目,directory entry,在目录条目中,文件目录和文件被当做是同一种对象来看待,也就是一致性,如果要查找某个文件目录中有什么东西,找到的可能是文件目录,有可能是文件,都是目录条目
和上面文件目录和文件都作为目录条目看待一样,将容器和内容作为同一种东西来看待,因为在容器中既可以放入内容,也可以放入子容器,在这个子容器中,又可以继续放入更小的容器,这样就形成了递归,容器结构
Composite组合模式就是用于创造出这样结构的模式,能够使容器和内容具有一致性,创造出递归结构的模式
下面一个具体的例子,列出文件目录和文件的列表,通过创建一个Entry抽象类,来将文件File类和文件目录Directory类统一起来,实现一致性
基本类如下:
Entry:抽象类,用来实现File类和Directory类的一致性
File:表示文件的类
Directory:表示文件目录的类
FileTreatementException:表示向文件中增加Entry时发生的异常的类
Main:main,测试类
首先是Entry抽象类,表示目录条目,File类和Directory类是它的子类
目录条目有一个名字,可以通过getName获取name,子类完成方法的实现
目录条目还有一个大小,通过getSize获取size,同样是子类完成方法的实现
add方法是为了向文件目录中放入文件和子文件目录,但是这个方法由Entry的子类Directory来实现,Entry里只需要抛出异常即可
printList方法显示文件目录中的内容,可以看到这里有方法重载,一个带了参数,一个不带参数;printList()是public,外部也可以调用,printList(String prefix)是protected,只能Entry类的子类调用
toString方法是将文件名和文件大小显示出来,调用的两个抽象方法交给子类去实现
package composite;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Entry
* Author: lihui
* Date: 2018/5/7 23:01
*/
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatementException {
throw new FileTreatementException();
}
public void printList() {
printList("");
}
protected abstract void printList(String prefix);
public String toString() {
return getName() + " (" + getSize() + ") ";
}
}
接着是File表示文件的类,Entry的子类
File类有两个字段,一个是文件名name,一个是文件大小size,getName和getSize实现了Entry抽象方法,分别返回文件名和文件大小
printList(String prefix)实现了Entry的抽象方法,注意这里出现了字符串+对象的格式,这样Java会自动调用对象的toString方法
package composite;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: File
* Author: lihui
* Date: 2018/5/7 23:01
*/
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
再接着Directory表示文件目录的类,Entry的子类
Directory类有两个字段,一个是文件目录的名字name,一个是directory,是个ArrayList,用来保存文件目录中的目录条目;getName返回文件目录的name,getSize会遍历directory字段中的所有元素,然后计算出它们大小的总和,这里entry.getSize()就包括了文件和文件目录,因为entry可能是File实例,也可能是Directory实例,不过没关系,这两个子类都实现了getSize方法,如果文件目录里又有子目录,就递归调用getSize
add方法向文件目录中加入文件或者子文件目录,不关心添加的到底是File实例还是Directory实例,直接调用ArrayList的add方法
printList(String prefix)实现了Entry的抽象方法,用于显示文件目录的目录条目,至于directory里到底是File实例还是Directory实例,不care,如果是文件就显示,如果是文件目录,就递归调用,最终显示
package composite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Directory
* Author: lihui
* Date: 2018/5/7 23:02
*/
public class Directory extends Entry {
private String name;
private List directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
@Override
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.printList(prefix + "/" + name);
}
}
}
FileTreatementException类,并非Java内置异常类,而是调用add方法抛出的异常
package composite;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: FileTreatementException
* Author: lihui
* Date: 2018/5/7 23:02
*/
public class FileTreatementException extends RuntimeException {
public FileTreatementException() {
}
public FileTreatementException(String msg) {
super(msg);
}
}
最后是Main
创建root,bin,tmp,usr四个文件目录,然后bin目录下创建vi和latex文件;接着usr文件目录下创建yuki,hanako,tomura文件目录,每个目录新建文件
package composite;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Main
* Author: lihui
* Date: 2018/5/7 23:02
*/
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
} catch (FileTreatementException e) {
e.printStackTrace();
}
}
}
Composite模式的角色:
Leaf:树叶,例子中的File类;表示内容的角色,但是该角色不能再放入其它对象了
Composite:组合,复合物,例子中的Directory类;表示容器的角色,可以继续放入Leaf角色和Composite角色
Component:例子中的Entry类;使Leaf角色和Composite角色具有一致性,并且是它们的超类
Client:例子中的Main类;使用Composite模式的角色
思考:
使用Composite模式可以使容器和内容具有一致性,也可以成为多个和单个的一致性,也就是多个对象结合在一起,当做一个对象进行处理
本例子中多了一个抛出异常的类,主要为了捕获add方法,下面是几种不同方法的结果
1:定义在Entry类,抛异常(本例子),真正能用到add方法的其实也只有Directory类,只要重写add方法,自行实现;而File类通过继承调用add方法,会抛出异常
2:定义在Entry类,不做任何处理
3:定义在Entry类中,但不实现,抽象方法add,那么子类按需求自行实现;但是这样就要求所有子类都必须实现add方法,可是File类其实并不需要add方法,也必须要实现
4:定义在Directory类中,因为只有它用得着,不过这样定义的话,假如是多态,add的是超类Entry类型的变量,必须要类型转换
一般来说,Composite组合模式,都适用于各种递归结构,比如树,窗口和子窗口等