委派模式
一、定义
委派模式又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与继承相同的代码重用。它的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理解为全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式,不属于GOF23种设计模式中。
委派模式有3个参与角色
- 抽象任务角色(ITask):定义一个抽象接口,它有若干实现类。
- 委派者角色(Delegate):负责在各个具体角色实例之间做出决策,判断并调用具体实现的方法。
- 具体任务角色(Concrete):真正执行任务的角色。
二、委派模式的应用场景
委派模式在业务场景中的例子很多:需要实现表现层和业务层之间的松耦合;需要编排多个服务之间的调用;需要封装一层服务查找和调用。前面说的都是业务场景,下面来说下生活场景中的例子,例如:大老板跟项目经理下了个任务,项目经理不可能自己亲自去做所有事吧,他肯定会把收到的任务进行分解,然后分给下面的员工下发任务,等员工把工作完成后,再把结果汇总向老板汇报
//抽象任务角色 public interface ITask { void doTask(String mission) throws IllegalAccessException, InstantiationException; }
//具体任务角色 ConcreteA public class ConcreteA implements ITask { @Override public void doTask(String mission) { System.out.println("我是员工A,我的工作是UI"); } }
//具体任务角色 ConcreteB public class ConcreteB implements ITask { @Override public void doTask(String mission) { System.out.println("我是员工B,我的工作是开发"); } }
//委派者角色 Delegate 经理 public class Delegate implements ITask{ private Map<String,Class> map=new HashMap<>(); public Delegate(){ map.put("UI",ConcreteA.class); map.put("开发",ConcreteB.class); } @Override public void doTask(String mission) throws IllegalAccessException, InstantiationException { if (!map.containsKey(mission)){ System.out.println("没有这样的业务员"); return; } ITask iTask= (ITask) map.get(mission).newInstance(); iTask.doTask(mission); } }
//老板 public class Robam { public void command(String mission,ITask iTask) throws InstantiationException, IllegalAccessException { iTask.doTask(mission); } }
public class Test { public static void main(String[] args) throws IllegalAccessException, InstantiationException { new Robam().command("UI",new Delegate()); } }
三、委派模式在源码中的体现
JDK中有一个典型的委派,JVM在加载类是用的双亲委派模型,一个类加载器在加载类时,先把这个请求委派给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委派,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载;从定义中可以看到双亲加载模型一个类加载器加载时,首先不是自己加载,而是委派给父加载器,下面看loadClass()方法的源码,此方法在ClassLoader中,在这个类里就定义了一个双亲,用于下面的类加载
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//先判断有没有父类
if (parent != null) {
//有就先调父类加载
c = parent.loadClass(name, false);
} else {
//自己加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
同样在Method类里常用的代理执行方法invoke()也存在类似的机制
@CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } //MethodAccessor没有做任何事情只是拿到了ma的返回结果而已 return ma.invoke(obj, args); }
IOC中对象实例化委派模式
在调用doRegisterBeanDefinitions()方法时即BeanDefinition进行注册的过程中,会设置BeanDefinitionParserDelegate类型的Delegate对象传给this.delegate,并将这个对象作为一个参数传给:parseBeanDefinitions(root, this.delegate)中,然后主要的解析的工作就是通过delegate作为主要角色来完成的,可以看到下方代码:
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //判断节点是否属于同一命名空间,是则执行后续的解析 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { //注解定义的Context的nameSpace进入到这个分支中 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
其中最终能够走到bean注册部分的是,会进入到parseDefaultElement(ele, delegate)中,然后针对不同的节点类型,针对bean的节点进行真正的注册操作,而在这个过程中,delegate会对element进行parseBeanDefinitionElement,得到了一个BeanDefinitionHolder类型的对象,之后通过这个对象完成真正的注册到Factory的操作
SpringMVC中,类DispatcherServlet
DispatcherServlet 虽然没带delegate,但也是委派模式的一种实现。
前端请求都统一走到DispatcherServlet 的doService()方法中,然后在doService()方法中调用doDispatch()方法,在doDispatch()方法中,会获取业务处理的handler,执行handle()方法处理请求。
doDispatch()方法核心源码截图
看过源码的人从上面逻辑可以知道用于HTTP请求处理程序/控制器的中央调度程序,针对通过WEB UI输入的url请求,委派给DispatcherServlet处理,从委派者的角度来看,关注结果即可
四、总结
优点:
通过任务委派能够将一个大型的任务细化,然后通过统一管理这些子任务的完成情况实现任务的跟进,能够加快任务执行的效率。
缺点:
任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下可能需要进行多重委派,容易造成紊乱。
委派模式与代理模式异同
代理模式是由代理来帮你完成一些工作,而这里的委派模式,是由委派对象来帮你完成一些工作,字面上来看,好像并没有什么差别。首先,我们代理可以增强我们的代理目标类,而委派模式,像上面的例子,老板要做一件事只用跟经理说下就行,接下来的所有的事情,都交给经理去处理即可了,自己完全不必实际去参与到行动中。
git源码:https://gitee.com/TongHuaShuShuoWoDeJieJu/design_pattern.git