合成模式,又有叫 组合模式的 , 也有叫 部分-整体模式的,反正叫啥都好,总离不开 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进行解析时一致的。