一、委派模式的定义
委派模式(Delegate Pattern)不属于GoF23种设计模式。其作用就是负责任务的调用和分配,和代理模式很相像,可以看作是一种特殊情况下的静态全权代理,但是代理模式注重过程,而委派模式注重结果。
二、委派模式的应用场景
委派模式在Spring中应用得非常多,常用的DispatcherServlet用到了委派模式。现实生活中也常有委派模式的应用场景,列如老板给技术总监下达任务,技术总监会根据实际情况给每个技术主管派发任务,然后每个技术主管再把任务拆解分配给每个员工,待员工把任务完成后,再逐级上报后,最后由技术总监向老板汇报最终结果。下面用代码来模拟一下这个业务场景。
创建根接口IEmployee员工接口:
public interface IEmployee {
public void doing(String command);
}
创建研发员工类DevelopEmployee:
public class DevelopEmployee implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是研发员工小张,我现在开始干" + command + "工作");
}
}
创建测试员工类TestEmployee:
public class TestEmployee implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是测试员工小白,我现在开始干" + command + "工作");
}
}
创建主管类Manager:
public class Manager implements IEmployee {
private Map<String,IEmployee> targets = new HashMap<String,IEmployee>();
public Manager() {
targets.put("develop", new DevelopEmployee());
targets.put("test", new TestEmployee());
}
@Override
public void doing(String command) {
System.out.println("技术主管管理员工");
targets.get(command).doing(command);
}
}
创建总监类Director:
public class Director implements IEmployee {
private Map<String,IEmployee> targets = new HashMap<String,IEmployee>();
public Director() {
targets.put("manager", new Manager());
}
@Override
public void doing(String command) {
Manager manager = (Manager) targets.get(command);
System.out.println("技术总监管理技术主管");
}
}
创建老板Boss类:
public class Boss {
public void command(String command, Director director) {
director.doing(command);
}
}
创建main方法:
public static void main(String[] args) {
Boss boss = new Boss();
boss.command("manager", new Director());
Manager manager = new Manager();
manager.doing("test");
manager.doing("develop");
}
通过上面的代码,生动地还原了研发主管分配工作的业务场景,也是委派模式的生动体现。来看一下完整的类图:

三、委派模式在源码中的体现
3.1 JVM的类加载
众所周知JVM在加载类的时候使用的双亲委派,那什么又是双亲委派呢?一个类在加载器在加载类的时,先把这个请求委派给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委派,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回;如果父类加载器无法完成加载,那么子加载器才回去尝试自己加载。双亲委派模型加载一个类加载器加载类时,首先不是自己加载,而是委派给父加载器。 下面来看下ClassLoader中的loadClass()方法的源码,这里就使用了双亲委派。
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
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()也存在双亲委派机制,来看下源码:
/**
* Invokes the underlying method represented by this {@code Method}
* object, on the specified object with the specified parameters.
* Individual parameters are automatically unwrapped to match
* primitive formal parameters, and both primitive and reference
* parameters are subject to method invocation conversions as
* necessary.
*
* <p>If the underlying method is static, then the specified {@code obj}
* argument is ignored. It may be null.
*
* <p>If the number of formal parameters required by the underlying method is
* 0, the supplied {@code args} array may be of length 0 or null.
*
* <p>If the underlying method is an instance method, it is invoked
* using dynamic method lookup as documented in The Java Language
* Specification, Second Edition, section 15.12.4.4; in particular,
* overriding based on the runtime type of the target object will occur.
*
* <p>If the underlying method is static, the class that declared
* the method is initialized if it has not already been initialized.
*
* <p>If the method completes normally, the value it returns is
* returned to the caller of invoke; if the value has a primitive
* type, it is first appropriately wrapped in an object. However,
* if the value has the type of an array of a primitive type, the
* elements of the array are <i>not</i> wrapped in objects; in
* other words, an array of primitive type is returned. If the
* underlying method return type is void, the invocation returns
* null.
*
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
* @return the result of dispatching the method represented by
* this object on {@code obj} with parameters
* {@code args}
*
* @exception IllegalAccessException if this {@code Method} object
* is enforcing Java language access control and the underlying
* method is inaccessible.
* @exception IllegalArgumentException if the method is an
* instance method and the specified object argument
* is not an instance of the class or interface
* declaring the underlying method (or of a subclass
* or implementor thereof); if the number of actual
* and formal parameters differ; if an unwrapping
* conversion for primitive arguments fails; or if,
* after possible unwrapping, a parameter value
* cannot be converted to the corresponding formal
* parameter type by a method invocation conversion.
* @exception InvocationTargetException if the underlying method
* throws an exception.
* @exception NullPointerException if the specified object is null
* and the method is an instance method.
* @exception ExceptionInInitializerError if the initialization
* provoked by this method fails.
*/
@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();
}
return ma.invoke(obj, args);
}
3.2 Spring与委派模式
在 Spring 源码中,只要以Delegate结尾的都是实现了委派模式。例如:BeanDefinitionParserDelegate 根据不同类型委派不同的逻辑解析 BeanDefinition。在Spring IOC中,在调用doRegisterBeanDefinitions()方法即BeanDefinition进行注册的过程中,会设置BeanDefinitionParseDelegate类型的Delegate对象传给this.delegate,并将这个对象作为一个参数传给parseBeanDefinitions(root, this.delegate)中,然后主要的解析工作就是通过delegate作为主要角色来完成的,来看下源代码吧:
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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
四、委派模式的优缺点
优点: 能够将一个大型任务细化,统一管理子任务的完成情况,并实现任务跟进,能够加快任务的执行效率。
缺点: 需要根据任务的复杂程度进行不同的改变,任务比较复杂的情况下可能需要多重委派,容易造成系统紊乱。