1. 程式人生 > 實用技巧 >03依賴注入--01控制反轉、IoC模式

03依賴注入--01控制反轉、IoC模式

控制反轉Inversion of Control

DI和IoC幾乎都是成對出現的,我們在理解依賴注入之前首先要弄明白什麼是IoC,也就是控制反轉,體現的就是控制權的轉移,即控制權原來在A中,現在需要B來接管。那麼在軟體中是如何實現的。通過一個例子來說明傳統設計在採用IoC之後是如何實現反轉的。

我們模擬了一個http請求流程,由其中5個核心任務組成。

namespace IoCDemo
{
  class Program
  {
    static async Task Main()
    {
      while (true)
      {
        var address = new Uri("http://127.0.0.1:8080/api/test");
        await MvcLib.ListenAsync(address);
        while (true)
        {
          var request = await MvcLib.ReceiceAsync();
          var controller = await MvcLib.CreateControllerAsync(request);
          var view = await MvcLib.ExecuteControllerAsync(controller);
          await MvcLib.RenderViewAsync(view);
        }
      }
    }
  }
  public static class MvcLib
  {
    /// <summary>
    /// 啟動一個監聽器並將其繫結到指定的地址進行http請求的監聽
    /// </summary>
    /// <param name="address"></param>
    /// <returns></returns>
    public static Task ListenAsync(Uri address)
    {
      return Task.CompletedTask;
    }
    /// <summary>
    /// 接收抵達的請求
    /// </summary>
    /// <returns>接收到的請求</returns>
    public static Task<HttpContext> ReceiceAsync()
    {
      return Task.FromResult(new HttpContext());
    }
    /// <summary>
    /// 根據接收的請求解析並激活目標Controller物件
    /// </summary>
    /// <param name="request"></param>
    /// <returns>啟用的Controller物件</returns>
    public static Task<Controller> CreateControllerAsync(HttpContext request)
    {
      return Task.FromResult(new Controller());
    }
    /// <summary>
    /// 執行啟用的物件Controller,返回檢視
    /// </summary>
    /// <param name="controller"></param>
    /// <returns>表示檢視的物件</returns>
    public static Task<View> ExecuteControllerAsync(Controller controller)
    {
      return Task.FromResult(new View());
    }
    /// <summary>
    /// 將檢視裝換成html請求
    /// </summary>
    /// <param name="view"></param>
    /// <returns></returns>
    public static Task RenderViewAsync(View view)
    {
      return Task.CompletedTask;
    }
  }
  public class View
  {
  }
  public class Controller
  {
  }
  public class HttpContext
  {
  }
}

上面的程式碼中,類庫MvcLib僅僅通過api的形式提供單一功能的實現,http請求的工作流是在應用程式中進行了實現。但是所有的http請求都是這個工作流,也就是說這個工作流沒有得到重用。那麼我們可以通過一個框架(Framework)來實現工作流,以達到工作流複用的目的。那麼應用程式只需要複用框架即可。這裡面就是將對工作流的控制放到了框架中,而不是應用程式自己來控制,這個過程其實就是將工作流的控制權從應用程式交到了框架中,也就實現了控制反轉。
如果我們將一個工作流程定義在框架中(A→B→C),建立在框架基礎上的兩個應用程式App1和App2需要對這個工作流進行自定義。框架驅動工作流自動執行,並且會按照執行前自定義好的工作流內容進行執行。

綜上,IoC一方面通過流程控制從應用程式向框架反轉,實現了針對流程的重用,另一方面通過內建的擴充套件機制是這個被重用的流程能夠自由的被定製。

IoC模式

IoC被視為一種設計原則,在很多設計模式中都有所體現。

模板方法

將一個可複用的工作流程或者由多個步驟組成的演算法定義成模板方法,組成這個流程或者演算法的單一步驟則在響應的虛方法中實現,模板方法根據預先編排的流程呼叫這些虛方法。這些方法均定義在一個類中,可以通過派生類並重寫響應的虛方法的方式達到對流程定製的目的。

#region 模板方法
/// <summary>
/// 將整個執行流程放到類中,在類中分別定義了5個虛方法。
/// 模板方法StartAsync根據預定義的請求處理流程先後呼叫這5個方法。
/// 在具體的應用場景中,只需要例項化MvcEngine,並呼叫StartAsync方法就可以滿足基本要求
/// </summary>
public class MvcEngine
{
  public async Task StartAsync(Uri address)
  {
    await ListenAsync(address);
    while (true)
    {
      var request = await ReceiceAsync();
      var controller = await CreateControllerAsync(request);
      var view = await ExecuteControllerAsync(controller);
      await RenderViewAsync(view);
    }
  }

  protected virtual Task ListenAsync(Uri address) => Task.CompletedTask;
  protected virtual Task<HttpContext> ReceiceAsync() => Task.FromResult(new HttpContext());
  protected virtual Task<Controller> CreateControllerAsync(HttpContext request) => Task.FromResult(new Controller());
  protected virtual Task<View> ExecuteControllerAsync(Controller controller) => Task.FromResult(new View());
  protected virtual Task RenderViewAsync(View view) => Task.CompletedTask;
}
/// <summary>
/// 如果請求環節無法滿足應用場景,可以建立派生類,並重寫某個環節的虛方法即可。
/// </summary>
public class FoobarMvcEngine : MvcEngine
{
  protected override Task<Controller> CreateControllerAsync(HttpContext request)
  {
    //此處省略了擴充套件實現
   
    return Task.FromResult(new Controller());
  }
}
#endregion

工廠方法

對於一個複雜的流程,可以將組成流程的各個環節實現在響應的元件之中,所以針對流程的定製可以通過提供響應的元件的形式來實現。工廠方法和抽象工廠都可以實現。

工廠方法就是在某個類中定義用來提供所需服務的方法,這個方法可以是一個單純的虛方法,也可以是具有預設實現的虛方法,至於方法宣告的返回型別,可以是一個介面或者抽象類,也可以是未封閉的(Sealed)具體型別。派生型別可以採用重寫工廠方法的方式提供所需的服務物件。

針對上面的MVC框架流程,將整個請求處理流程獨立成幾個核心環節,核心環節對應不同的介面。

#region 工廠方法

/// <summary>
/// 監聽、接收和響應請求
/// </summary>
public interface IWebListener
  {
    Task ListenAsync(Uri address);
    Task<HttpContext> ReceiceAsync();
  }
public class WebListener : IWebListener
  {
    public Task ListenAsync(Uri address) => Task.CompletedTask;
    public Task<HttpContext> ReceiceAsync() => Task.FromResult(new HttpContext());
  }

/// <summary>
/// 根據當前上下文啟用目標Controller物件,並做一些釋放回收的工作
/// </summary>
public interface IControllerActivator
  {
    Task<Controller> CreateControllerAsync(HttpContext request);
    Task ReleaseAsync(Controller controller);
  }
public class ControllerActivator : IControllerActivator
  {
    public Task<Controller> CreateControllerAsync(HttpContext request) => Task.FromResult(new Controller());
    public Task ReleaseAsync(Controller controller) => Task.CompletedTask;
  }

/// <summary>
/// 針對Controller的執行
/// </summary>
public interface IControllerExecutor
  {
    Task<View> ExecuteAsync(Controller controller);
  }
public class ControllerExecutor : IControllerExecutor
  {
    public Task<View> ExecuteAsync(Controller controller) => Task.FromResult(new View());
  }

/// <summary>
/// 檢視的呈現
/// </summary>
public interface IViewRender
  {
    Task RenderViewAsync(View view);
  }
public class ViewRender : IViewRender
  {
    public Task RenderViewAsync(View view) => Task.CompletedTask;
  }

/// <summary>
/// 採用預設的實現,實現預定的流程
/// </summary>
public class MvcEngine1
  {
    public async Task StartAsync(Uri address)
    {
      var listener = GetWebListener();
      var controllerActivator = GetControllerActivator();
      var controllerExecutor = GetControllerExecutor();
      var viewRender = GetIViewRender();

      await listener.ListenAsync(address);
      while (true)
      {
        var request = await listener.ReceiceAsync();
        var controller = await controllerActivator.CreateControllerAsync(request);
        try
        {
          var view = await controllerExecutor.ExecuteAsync(controller);
          await viewRender.RenderViewAsync(view);
        }
        finally
        {
          await controllerActivator.ReleaseAsync(controller);
        }

      }
    }

    protected virtual IWebListener GetWebListener() => new WebListener();
    protected virtual IControllerActivator GetControllerActivator() => new ControllerActivator();
    protected virtual IControllerExecutor GetControllerExecutor() => new ControllerExecutor();
    protected virtual IViewRender GetIViewRender() => new ViewRender();
  }

/// <summary>
/// 定製化的IControllerActivator流程
/// 需要根據需要實現介面
/// </summary>
public class ControllerExActivator : IControllerActivator
  {
    public Task<Controller> CreateControllerAsync(HttpContext request)
    {
      //省略實現

      return Task.FromResult(new Controller());
    }
    public Task ReleaseAsync(Controller controller)
    {
      //省略實現

      return Task.CompletedTask;
    }
  }

/// <summary>
/// 如果請求環節無法滿足應用場景,可以建立派生類,並重寫某個環節的工廠方法即可。
/// </summary>
public class FoobarMvcEngine1 : MvcEngine1
  {
    protected override IControllerActivator GetControllerActivator()
    {
      return new ControllerExActivator();
    }
  }

#endregion

抽象工廠

工廠方法和抽象工廠都能夠生產物件例項,但是兩者本質上有區別。工廠方法利用定義在某個型別的抽象方法或者虛方法完成針對單一物件的提供,而抽象工廠是利用一個獨立的介面或者抽象類提供一組相關的物件

定義了一個獨立的工廠介面或者抽象工廠類,並在其中定義多個工廠方法來提供多個相關物件。如果希望抽象工廠具有一組預設的輸出,可以將一個未封閉的型別作為抽象工廠,以虛方法的形式定義預設實現來返回物件。在具體的開發中,可以實現工廠介面或者繼承抽象工廠類,來實現具體的工廠類,然後提供一系列物件。

#region 抽象工廠

/// <summary>
/// 抽象工廠會建立多個例項
/// </summary>
public interface IMvcEngineFactory
  {
    IWebListener GetWebListener();
    IControllerActivator GetControllerActivator();
    IControllerExecutor GetControllerExecutor();
    IViewRender GetIViewRender();
  }
/// <summary>
/// 預設實現
/// </summary>
public class MvcEngineFactory : IMvcEngineFactory
  {
    public virtual IWebListener GetWebListener() => new WebListener();
    public virtual IControllerActivator GetControllerActivator() => new ControllerActivator();
    public virtual IControllerExecutor GetControllerExecutor() => new ControllerExecutor();
    public virtual IViewRender GetIViewRender() => new ViewRender();
  }

/// <summary>
/// 採用預設的實現,實現預定的流程
/// </summary>
public class MvcEngine2
  {
    public IMvcEngineFactory _engineFactory { get; }

    public MvcEngine2(IMvcEngineFactory engineFactory)
    {
      _engineFactory = engineFactory ?? new MvcEngineFactory();
    }

    public async Task StartAsync(Uri address)
    {
      var listener = _engineFactory.GetWebListener();
      var controllerActivator = _engineFactory.GetControllerActivator();
      var controllerExecutor = _engineFactory.GetControllerExecutor();
      var viewRender = _engineFactory.GetIViewRender();

      await listener.ListenAsync(address);
      while (true)
      {
        var request = await listener.ReceiceAsync();
        var controller = await controllerActivator.CreateControllerAsync(request);
        try
        {
          var view = await controllerExecutor.ExecuteAsync(controller);
          await viewRender.RenderViewAsync(view);
        }
        finally
        {
          await controllerActivator.ReleaseAsync(controller);
        }

      }
    }
  }

/// <summary>
/// 如果請求環節無法滿足應用場景,可以建立派生類,並重寫某個環節的虛方法即可。
/// </summary>
public class FoobarMvcEngineFactory : MvcEngineFactory
  {
    public override IControllerActivator GetControllerActivator()
    {
      return new ControllerExActivator();
    }
  }



#endregion