委派模式

一、定义

委派模式又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与继承相同的代码重用。它的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理解为全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式,不属于GOF23种设计模式中。

委派模式有3个参与角色

  1. 抽象任务角色(ITask):定义一个抽象接口,它有若干实现类。
  2. 委派者角色(Delegate):负责在各个具体角色实例之间做出决策,判断并调用具体实现的方法。
  3. 具体任务角色(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源码://github.com/ljx958720/design_patterns.git