依賴注入[2]: 基於IoC的設計模式
正如我們在《控制反轉》提到過的,很多人將IoC理解為一種“面向物件的設計模式”,實際上IoC自身不僅與面向物件沒有必然的聯絡,它也算不上是一種設計模式。一般來講,設計模式提供了一種解決某種具體問題的方案,但是IoC既沒有一個針對性的問題領域,其自身沒有提供一種可實施的解決方案,所以我更加傾向於將IoC視為一種設計原則。實際上很多我們熟悉的設計模式背後採用了IoC原則,接下來我們就來介紹幾種典型的“設計模式”。
一、模板方法
提到IoC,很多人首先想到的是DI,但是在我看來與IoC思想最為接近的倒是另一種被稱為“模板方法(Template Method)”的設計模式。模板方法模式與IoC的意圖可以說不謀而合,該模式主張將一個可複用的工作流程或者由多個步驟組成的演算法定義成模板方法,組成這個流程或者演算法的步驟實現在相應的虛方法之中,模板方法根據按照預先編排的流程去呼叫這些虛方法。所有這些方法均定義在同一個類中,我們可以通過派生該類並重寫相應的虛方法達到對流程定製的目的。
對於《控制反轉》演示的這個MVC的例子,我們可以將整個請求處理流程實現在如下一個MvcEngine類中,請求的監聽與接收、目標Controller的啟用與執行以及View的呈現分別定義在5個受保護的虛方法中,模板方法StartAsync根據預定義的請求處理流程先後呼叫這5個方法。
public class MvcEngine { public async Task StartAsync(Uri address) { await ListenAsync(address); while (true) { var request = awaitReceiveAsync(); var controller = await CreateControllerAsync(request); var view = await ExecuteControllerAsync(controller); await RenderViewAsync(view); } } protected virtual Task ListenAsync(Uri address); protected virtual Task<Request> ReceiveAsync();protected virtual Task<Controller> CreateControllerAsync(Request request); protected virtual Task<View> ExecuteControllerAsync(Controller controller); protected virtual Task RenderViewAsync(View view); }
對於具體的應用來說,如果定義在MvcEngine針對請求的處理方式完全符合它的要求,它只需要建立這個一個MvcEngine物件,然後指定一個對應的基地址呼叫模板方法StartAsync開啟這個MVC引擎即可。如果該MVC引擎處理請求的某個環節不能滿足它的要求,它可以建立MvcEngine的派生類,並重寫實現該環節的相應虛方法即可。
比如說定義在某個應用程式中的Controller都是無狀態的,它希望採用單例(Singleton)的方式重用已經啟用的Controller以提高效能,那麼它就可以按照如下的方式建立一個自定義的FoobarMvcEngine並按照自己的方式重寫
public class FoobarMvcEngine : MvcEngine { protected override Task<View> CreateControllerAsync (Request request) { <<省略實現>> } }
二、工廠方法
對於一個複雜的流程來說,我們傾向於將組成該流程的各個環節實現在相對獨立的元件之中,那麼針對流程的定製就可以通過提供定製元件的方式來實現。我們知道23種設計模式之中有一種重要的型別,那就是“建立型模式”,比如常用的“工廠方法”和“抽象工廠”,IoC所體現的針對流程的共享與定製可以通過它們來完成。
所謂的工廠方法,說白了就是在某個類中定義用於提供依賴物件的方法,這個方法可以是一個單純的虛方法,也可以是具有預設實現的虛方法,至於方法宣告的返回型別,可以是一個介面或者抽象類,也可以是未被封閉(Sealed)的具體型別。作為它的派生型別,它可以實現或者重寫工廠方法以提供所需的具體物件。
同樣以我們的MVC框架為例,我們讓獨立的元件來完成組成整個請求處理流程的幾個核心環節。具體來說,我們針對這些核心元件定義瞭如下這幾個對應的介面。IWebLister介面用來監聽、接收和響應請求(針對請求的響應由ReceiveAsync方法返回的HttpContext物件來完成,後者表示針對當前請求的上下文),IControllerActivator介面用於根據當前請求啟用目標Controller物件,已經在後者執行完成後做一些釋放回收工作。至於IControllerExecutor和IViewRender介面則分別用來完成針對Controller的執行和針對View的呈現。
public interface IWebLister
{
Task ListenAsync(Uri address);
Task<HttpContext> ReceiveAsync();
}
public interface IControllerActivator
{
Task<Controller> CreateControllerAsync(HttpContext httpContext);
Task ReleaseAsync(Controller controller);
}
public interface IControllerExecutor
{
Task<View> ExecuteAsync(Controller controller, HttpContext httpContext);
}
public interface IViewRender
{
Task RendAsync(View view, HttpContext httpContext);
}
我們在作為MVC引擎的MvcEngine類中定義了四個工廠方法(GetWebListener、GetControllerActivator、GetControllerExecutor和GetViewRenderer)來提供上述這4種元件。這四個工廠方法均為具有預設實現的虛方法,我們可以利用它們提供預設的元件。在用於啟動引擎的StartAsync方法中,我們利用這些工廠方法提供的物件來具體完成請求處理流程的各個核心環節。
public class MvcEngine
{
public async Task StartAsync(Uri address)
{
var listener = GetWebLister();
var activator = GetControllerActivator();
var executor = GetControllerExecutor();
var render = GetViewRender();
await listener.ListenAsync(address);
while (true)
{
var httpContext = await listener.ReceiveAsync();
var controller = await activator.CreateControllerAsync(httpContext);
try
{
var view = await executor.ExecuteAsync(controller, httpContext);
await render.RendAsync(view, httpContext);
}
finally
{
await activator.ReleaseAsync(controller);
}
}
}
protected virtual IWebLister GetWebLister();
protected virtual IControllerActivator GetControllerActivator();
protected virtual IControllerExecutor GetControllerExecutor();
protected virtual IViewRender GetViewRender();
}
對於具體的應用程式來說,如果需要對請求處理的某個環節進行定製,它需要將定製的操作實現在對應介面的實現類中。在MvcEngine的派生類中,我們需要重寫對應的工廠方法來提供被定製的物件。 比如上面提及的以單例模式提供目標Controller物件的實現就定義在SingletonControllerActivator類中,我們在派生於MvcEngine的FoobarMvcEngine類中重寫了工廠方法GetControllerActivator使其返回一個SingletonControllerActivator物件。
public class SingletonControllerActivator : IControllerActivator { public Task<Controller> CreateControllerAsync(HttpContext httpContext) { <<省略實現>> } public Task ReleaseAsync(Controller controller) => Task.CompletedTask; } public class FoobarMvcEngine : MvcEngine { protected override ControllerActivator GetControllerActivator() => new SingletonControllerActivator(); }
三、抽象工廠
雖然工廠方法和抽象工廠均提供了一個“生產”物件例項的工廠,但是兩者在設計上卻有本質的不同。工廠方法利用定義在某個型別的抽象方法或者虛方法實現了針對單一物件提供方式的抽象,而抽象工廠則利用一個獨立的介面或者抽象類來提供一組相關的物件。
具體來說,我們需要定義一個獨立的工廠介面或者抽象工廠類,並在其中定義多個的工廠方法來提供“同一系列”的多個相關物件。如果希望抽象工廠具有一組預設的“產出”,我們也可以將一個未被封閉的具體類作為抽象工廠,以虛方法形式定義的工廠方法將預設的物件作為返回值。我們根據實際的需要通過實現工廠介面或者繼承抽象工廠類(不一定是抽象類)定義具體工廠類來提供一組定製的系列物件。
現在我們採用抽象工廠模式來改造我們的MVC框架。如下面的程式碼片段所示,我們定義了一個名為IMvcEngineFactory的介面作為抽象工廠,定義在其中定義了四個方法來提供請求監聽和處理過程使用到的4種核心物件。如果MVC提供了針對這四種核心元件的預設實現,我們可以按照如下的方式為這個抽象工廠提供一個預設實現(MvcEngineFactory)。
public interface IMvcEngineFactory
{
IWebLister GetWebLister();
IControllerActivator GetControllerActivator();
IControllerExecutor GetControllerExecutor();
IViewRender GetViewRender();
}
public class MvcEngineFactory: IMvcEngineFactory
{
IWebLister GetWebLister();
IControllerActivator GetControllerActivator();
IControllerExecutor GetControllerExecutor();
IViewRender GetViewRender();
}
現在我們採用抽象工廠模式來改造我們的MVC框架。我們在建立MvcEngine物件可以提供一個具體的IMvcEngineFactory物件,如果沒有顯式指定,MvcEngine會使用預設的EngineFactory物件。在用於啟動引擎的StartAsync方法中,MvcEngine利用IMvcEngineFactory來獲取相應的物件協作完整對請求的處理流程。
public class MvcEngine
{
public IMvcEngineFactory EngineFactory { get; }
public MvcEngine(IMvcEngineFactory engineFactory = null)
=> EngineFactory = engineFactory??new MvcEngineFactory();
public async Task StartAsync(Uri address)
{
var listener = EngineFactory.GetWebLister();
var activator = EngineFactory.GetControllerActivator();
var executor = EngineFactory.GetControllerExecutor();
var render = EngineFactory.GetViewRender();
await listener.ListenAsync(address);
while (true)
{
var httpContext = await listener.ReceiveAsync();
var controller = await activator.CreateControllerAsync(httpContext);
try
{
var view = await executor.ExecuteAsync(controller, httpContext);
await render.RendAsync(view, httpContext);
}
finally
{
await activator.ReleaseAsync(controller);
}
}
}
}
如果具體的應用程式需要採用上面定義的SingletonControllerActivator以單例的模式來啟用目標Controller,我們可以按照如下的方式定義一個具體的工廠類FoobarEngineFactory。最終的應用程式將這麼一個FoobarEngineFactory物件作為MvcEngine的EngineFactory。
public class FoobarEngineFactory : EngineFactory
{
public override ControllerActivator GetControllerActivator()
{
return new SingletonControllerActivator();
}
}
public class App
{
static void Main(string[] args)
{
Uri address = new Uri("http://0.0.0.0:8080/mvcapp");
MvcEngine engine = new MvcEngine(new FoobarEngineFactory());
engine.Start(address);
}
}
除了上面介紹這三種典型的設計,還有很多其他的設計模式,比如策略模式、觀察者模式等等,它們無一不是採用IoC的設計原則。Martin Fowler在《Inversion of Control 》一文中正是通過觀察者模式來介紹IoC的。我們將在下一篇中對依賴注入模式進行深入講解。