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