合成模式,又有叫 组合模式的 , 也有叫 部分-整体模式的,反正叫啥都好,总离不开 Composite 这个单词。
图解设计模式一书中,将合成模式归纳在 “一致性”一栏,合成模式的设计意图是 能够使容器与内容具有一致性,再通俗点就是 保证调用单对象与组合对象的一致性。
合成模式UML类图如上
Component(抽象构件):抽象类或者接口,给出公共的行为;
Leaf(树叶构件):树叶构件一般不需要下级角色了,代表各色各样具体的抽象构件的实现类;
Composite(树枝构件):就像树枝上挂着一些列抽象构件,可能是叶子,也可能还挂着树枝。树枝构件维护着抽象构件的组合,而不单单是树叶构件的组合。
最常见的合成模式可能就是:计算机文件系统,目录下面可能存文件,可能存文件夹,文件夹里面呢,可以存一堆目录;
以文件系统记录一个例子:
把目录抽象出来作为抽象构件,文件作为树叶构件,而文件夹作为树枝构件;
目录Entry:
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws Exception{
throw new RuntimeException("我没法添加目录条目");
}
public void printList(){
printList("");
}
protected abstract void printList(String prefix);
@Override
public String toString() {
return getName()+"("+getSize()+")";
}
}
文件File:
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:
public class Directory extends Entry{
private String name;
public Directory(String name) {
this.name = name;
}
private ArrayList directory=new ArrayList();
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size=0;
Iterator iterator = directory.iterator();
while(iterator.hasNext()){
Entry entry = (Entry) iterator.next();
size+=entry.getSize();
}
return size;
}
@Override
public Entry add(Entry entry) throws Exception {
directory.add(entry);
return this;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix+"/"+this);
Iterator iterator = directory.iterator();
while(iterator.hasNext()){
Entry entry = (Entry) iterator.next();
entry.printList(prefix+"/"+name);
}
}
}
测试方法:
public static void main(String[] args) {
System.out.println("Making root entries....");
Directory root=new Directory("root");
Directory bin=new Directory("bin");
Directory tmp=new Directory("tmp");
Directory usr=new Directory("usr");
try {
root.add(bin);
root.add(tmp);
root.add(usr);
File vi = new File("vi", 10000);
bin.add(vi);
bin.add(new File("latex",20000));
root.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");
usr.add(yuki);
usr.add(hanako);
usr.add(tomura);
yuki.add(new File("diary.html",100));
File javaFile = new File("Composite.java", 200);
yuki.add(javaFile);
hanako.add(new File("memo.tex",300));
tomura.add(new File("game.doc",400));
tomura.add(new File("junk.mail",500));
root.printList();
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果:
我们通过getSize方法时候,不需要关心这是个File还是Directory,这就是容器与内容一致性的体现。 另外,文件系统可以获取文件当前绝对路径,程序需要改造下:
Entry类增加方法设置上级目录方法以及获取路径方法:
File类具体实现方法:
Directory类改造方法:
这样就是一个简略的文件的系统,支持获取文件绝对路径。
合成模式又被分为透明式合成模式、安全式合成模式
上例 Entry中有add(Entry entry)这样一个接口暴露出来,我们就有可能 在一个文件里添加一个文件,就可能出现程序错误,这就是不安全的合成模式,透明合成模式;透明合成模式将所有的接口方法都作为Component抽象构件的方法来设计;
而安全式合成模式呢,就是将add方法移到Directory类中实现,SpringMvc中的HandlerMethodArgumentResolverComposite就是采用安全式合成模式;
无论哪种设计模式,容器与内容对外都要体现一致性。 比如文件和文件夹删除,我调用文件的删除方法,把这个文件删除即可,删除文件夹,遍历文件夹维护的那个抽象构件集合,递归删除。
Spring中合成模式一瞥
Spring中经常可见某个类后缀为Composite,看起来给人一眼就是合成模式的感觉。
SpringMvc中有这样一个接口HandlerMethodArgumentResolver,作用就是用来解析@RequestMapping方法入参。 HandlerMethodArgumentResolver作为抽象构件,
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
树叶构件就是HandlerMethodArgumentResolver接口的一大堆实现类,解析各种各样的 方法入参,HandlerMethodArgumentResolverComposite就是树枝构件,维护着一个HandlerMethodArgumentResolver的集合,并且将添加抽象构件的方法封装到了HandlerMethodArgumentResolverComposite内部,属于安全式合成模式。
想要对方法入参进行解析,调用HandlerMethodArgumentResolverComposite遍历HandlerMethodArgumentResolver集合进行解析,和遍历HandlerMethodArgumentResolver进行解析时一致的。