一、定義
委派模式又叫委託模式,是一種物件導向的設計模式,允許物件組合實現與繼承相同的程式碼重用。它的基本作用就是負責任務的呼叫和分配任務,是一種特殊的靜態代理,可以理解為全權代理,但是代理模式注重過程,而委派模式注重結果。委派模式屬於行為型模式,不屬於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處理,從委派者的角度來看,關注結果即可
四、總結
優點:
通過任務委派能夠將一個大型的任務細化,然後通過統一管理這些子任務的完成情況實現任務的跟進,能夠加快任務執行的效率。
缺點:
任務委派方式需要根據任務的複雜程度進行不同的改變,在任務比較複雜的情況下可能需要進行多重委派,容易造成紊亂。
委派模式與代理模式異同
代理模式是由代理來幫你完成一些工作,而這裡的委派模式,是由委派物件來幫你完成一些工作,字面上來看,好像並沒有什麼差別。首先,我們代理可以增強我們的代理目標類,而委派模式,像上面的例子,老闆要做一件事只用跟經理說下就行,接下來的所有的事情,都交給經理去處理即可了,自己完全不必實際去參與到行動中。