委派模式

童話述說我的結局發表於2021-05-01

一、定義

委派模式又叫委託模式,是一種物件導向的設計模式,允許物件組合實現與繼承相同的程式碼重用。它的基本作用就是負責任務的呼叫和分配任務,是一種特殊的靜態代理,可以理解為全權代理,但是代理模式注重過程,而委派模式注重結果。委派模式屬於行為型模式,不屬於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原始碼:https://github.com/ljx958720/design_patterns.git

相關文章