4

[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式

 3 years ago
source link: https://www.cnblogs.com/artech/p/inside-asp-net-core-03-02.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

正如我们在《依赖注入:控制反转》提到过的,很多人将IoC理解为一种“面向对象的设计模式”,实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式。一般来讲,设计模式提供了一种解决某种具体问题的方案,但是IoC既没有一个针对性的问题领域,其自身也没有提供一种可操作性的解决方案,所以我们更加倾向于将IoC视为一种设计原则。很多我们熟悉的设计模式背后都采用了IoC原则,接下来我们就来介绍几种典型的“设计模式”。

一、模板方法

提到IoC,很多人首先想到的是依赖注入,但是在我看来与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 = await ReceiveAsync();
            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并按照自己的方式重写CreateControllerAsync方法即可。

public class FoobarMvcEngine : MvcEngine
{
    protected override Task<View> CreateControllerAsync (Request request)
    {
        <<省略实现>>
    }
}

二、工厂方法

对于一个复杂的流程来说,我们倾向于将组成该流程的各个环节实现在相应的组件之中,那么针对流程的定制就可以通过提供相应组件的形式来实现。我们知道23种设计模式之中有一种重要的类型,那就是“创建型模式”,比如常用的“工厂方法”和“抽象工厂”,IoC所体现的针对流程的共享与定制同样可以通过这些设计模式来完成。

所谓的工厂方法,说白了就是在某个类中定义用来提供所需服务对象的方法,这个方法可以是一个单纯的虚方法,也可以是具有默认实现的虚方法。至于方法声明的返回类型,可以是一个接口或者抽象类,也可以是未封闭(Sealed)的具体类型。作为它的派生类型,可以实现或者重写工厂方法以提供所需的服务对象。

同样以我们的MVC框架为例,我们让独立的组件来完成整个请求处理流程的几个核心环节。具体来说,我们为这些核心组件定义了如下几个对应的接口。IWebListener接口用来监听、接收和响应请求(针对请求的响应由ReceiveAsync方法返回的HttpContext上下文来完成),IControllerActivator接口用于根据当前HttpContext上下文激活目标Controller对象,并在Controller对象执行后做一些释放回收工作。至于IControllerExecutor和IViewRender接口则分别用来完成针对Controller的执行和针对View的呈现。

public interface IWebListener
{
    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 IViewRenderer
{
    Task RenderAsync(View view, HttpContext httpContext);
}

我们在作为MVC引擎的MvcEngine中定义了四个工厂方法(GetWebListener、GetControllerActivator、GetControllerExecutor和GetViewRenderer)来提供上述这四种组件。这四个工厂方法均为具有默认实现的虚方法,我们可以利用它们提供默认的组件。在用于启动引擎的StartAsync方法中,我们利用这些工厂方法提供的对象来具体完成整个请求处理流程。

public class MvcEngine
{
    public async Task StartAsync(Uri address)
    {
        var listener = GetWebListener();
        var activator = GetControllerActivator();
        var executor = GetControllerExecutor();
        var renderer = 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 renderer.RendAsync(view, httpContext);
            }
            finally
            {
                await activator.ReleaseAsync(controller);
            }
        }
    }
    protected virtual IWebLister GetWebListener(); 
    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的接口作为抽象工厂,并在其中定义了四个方法来提供请求监听和处理过程使用到的四种核心对象。如果MVC提供了针对这四种核心组件的默认实现,我们可以按照如下的方式为这个抽象工厂提供一个默认实现(MvcEngineFactory)。

public interface IMvcEngineFactory
{
    IWebLister GetWebListener();
    IControllerActivator GetControllerActivator();
    IControllerExecutor GetControllerExecutor();
    IViewRender GetViewRender();
}

public class MvcEngineFactory: IMvcEngineFactory
{
    public virtual IWebListener GetWebListener();
    public virtual IControllerActivator GetControllerActivator();
    public virtual IControllerExecutor GetControllerExecutor();
    public virtual IViewRenderer GetViewRenderer();
}

现在我们采用抽象工厂模式来改造我们的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.GetWebListener();
        var activator = EngineFactory.GetControllerActivator();
        var executor = EngineFactory.GetControllerExecutor();
        var renderer= EngineFactory.GetViewRenderer();

        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对象即可。

public class FoobarEngineFactory : EngineFactory
{
    public override ControllerActivator GetControllerActivator()
    {
        return new SingletonControllerActivator();
    }
}

public class App
{
    static async Task Main()
    {
        var address = new Uri("http://0.0.0.0:8080/mvcapp");
        var engine = new MvcEngine(new FoobarEngineFactory());
        await engine.StartAsync(address);
        ...
    }
}

[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配


Recommend

  • 16

    写在前面 上一篇大家已经粗略接触了解到.NET Core中间件的使用:ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件,

  • 9

    [ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转 ASP.NET Co...

  • 7

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在前面介绍的模板方法、工厂方法和抽象工...

  • 4

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了“标准化”,我们将这些标准化的组件称为服务,ASP.NET在内部专门维护了一个DI容器来提供所需...

  • 5

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象。ASP.NET Core中的DI容器体现为一个实现了IServiceProvider接口的对象。

  • 6

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用“好莱坞原则”是应用程序以被动的方式实现对流程的定制。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在

  • 6

    【ASP.NET Core】选项类的依赖注入 咱们继续上一个话题...

  • 8

    1. Ioc 与 DI Ioc 和DI 这两个词大家都应该比较熟悉,这两者已经在各种开发语言各种框架中普遍使用,成为框架中的一种基本设施了。 Ioc 是控制反转, Inversion of Control 的缩写,DI 是依赖注入,Inject Dependency 的缩写。...

  • 7

    .NET Core 依赖注入的基本用法 话接上篇,这一章介绍 .NET Core 框架自带的轻量级 Ioc 容器下服务使用的一些知识点,大家可以先看看上一篇文章 [ASP.NET Core - 依赖注入(一)] 2.3 服务...

  • 4

    4. ASP.NET Core默认服务 之前讲了中间件,实际上一个中间件要正常进行工作,通常需要许多的服务配合进行,而中间件中的服务自然也是通过 Ioc 容器进行注册和注入的。前面也讲到,按照约定中间件的封装一般会提供一个 User{Midd...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK