架构师内功心法,不属于GoF23种设计模式的委派模式详解

244 阅读6分钟

一、委派模式的定义

委派模式(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);
    }

}

四、委派模式的优缺点

优点: 能够将一个大型任务细化,统一管理子任务的完成情况,并实现任务跟进,能够加快任务的执行效率。

缺点: 需要根据任务的复杂程度进行不同的改变,任务比较复杂的情况下可能需要多重委派,容易造成系统紊乱。