前言
ASP.NET Core 在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务, 为了方便对这些组件进行定制, ASP.NET 通过定义接口的方式对它们进行了 "标准化", 我们将这些标准化的组件称为服务, ASP.NET 在内部专门维护了一个 DI 容器来提供所需的服务. 要了解这个 DI 容器以及现实其中的服务提供机制, 我们先得知道什么是 DI(Dependence Injection), 而一旦我们提到 DI, 又不得不说 IoC(Inverse of Control).
一, 流程控制的反转
我听到很多人将 IoC 说成是一种 "面向对象的设计模式", 但在我个人看来 IoC 不能算作一种 "设计模式", 其自身也与 "面向对象" 没有直接的关系. 很多人之所以不能很准确地理解 IoC 源于他们忽略了一个最根本的东西, 那就是 IoC 这个短语. 换句话说, 很多人之所以对 IoC 产生了诸多误解是因为他们忽略了 IoC 的定义.
IoC 的全名 Inverse of Control, 翻译成中文就是 "控制反转" 或者 "控制倒置". 控制反转也好, 控制倒置也罢, 它体现的意思是控制权的转移, 即原来控制权在 A 手中, 现在需要 B 来接管. 那么具体对于软件设计来说, IoC 所谓的控制权的转移具有怎样的体现呢? 要回答这个问题, 就需要先了解 IoC 的 C(Control)究竟指的是怎样一种控制, 在我看来这里所谓的控制更多地体现为一种 "流程的控制".
我们通过一个具体事例来说明传统的设计在采用了 IoC 之后针对流程的控制是如何实现反转的. 比如说我们现在设计一个针对 web 的 MVC 类库, 不妨将其命名为 MvcLib.MvcLib 提供了如下所示的 API 帮助我们完成整个 HTTP 请求流程中的主要任务. 具体来说, ListenAndReceiveRequest 方法启动一个监听器绑定到指定的地址进行请求的监听, 接收到的请求通过一个 Request 对象返回. ActivateController 方法根据接收到的请求解析并激活请求的目标 Controller.ExecuteContrller 方法执行激活的 Controller 并返回一个表示视图的 View 对象. RenderView 最终将 View 对象转换成 html 并作为当前请求响应的内容.
- public static class MvcLib
- {
- public static Request ListenAndReceiveRequest(Uri address);
- public static Controller ActivateController (Request request);
- public static View ExecuteContrller(Controller controller);
- public static void RenderView(View view);
- }
现在我们在这个 MvcLib 的基础上创建一个真正的 MVC 应用, 那么除了按照 MvcLib 的规范自定义具体的 Controller 和 View 之外, 我们还需要自行控制从请求的监听与接收, Controller 的激活与执行以及 View 的最终呈现在内的整个流程, 这样一个执行流程反映在如下所示的代码中.
- public class App
- {
- static void Main(string[] args)
- {
- Uri address = new Uri("http://localhost/mvcapp");
- while (true)
- {
- Request request = MvcLib.ListenAndReceiveRequest(address);
- Task.Run(()=> ProcessRequest(request));
- }
- }
- private static void ProcessRequest(Request request)
- {
- Controller controller = MvcLib.ActiveController(request);
- View view = MvcLib.ExecuteContrller(controller);
- MvcLib.RenderView(view);
- }
- }
这个例子体现了右如图所示的流程控制方式, 即我们设计的类库 (MvcLib) 仅仅通过 API 的形式提供某种单一功能的实现, 作为类库消费者的应用程序 (App) 则需要自行编排整个工作流程. 如果从重用的角度来讲, 这里被重用的仅限于实现某个环节单一功能的代码, 编排整个工作流程的代码并没有得到重用.
当我们构建一个应用的时候, 我们不仅仅是需要一个能够提供 API 的类库, 实际上更理想的形式是直接在一个现有的框架上构架我们的应用. 类库 (Library) 和框架 (Framework) 的不同之处在于, 前者往往只是提供实现某种单一功能的 API, 而后者则针对一个目标任务对这些单一功能进行编排形成一个完整的流程, 这个流程在一个引擎的驱动下被执行.
在我们上面演示 MvcLib 来说, 使用它的应用程序需要自行控制整个 HTTP 请求的处理流程, 实际上这是一个很 "泛化" 的工作流程, 几乎所有的 MVC 应用均采用这样的流程监听, 接收请求并最终对请求予以响应. 如果我们将这个流程实现在一个 MVC 框架之中, 由它构建的所有 MVC 应用就可以直接使用这个请求处理流程, 而并不需要自行重复实现它.
现在我们将 MvcLib 从类库改造成一个框架, 并姑且将其称为 MvcFrame. 如左图所示, MvcFrame 的核心是一个被称为 MvcEngine 的执行引擎, 它驱动一个编排好的工作流对 HTTP 请求进行一致性处理. 如果我们利用 MvcFrame 构建一个具体的 MVC 应用, 除了根据我们的业务需求定义相应的 Controller 和 View 之外, 我们只需要初始化这个引擎并直接启动它即可. 如果你曾经开发过 ASP.NET MVC 应用, 你会发现 ASP.NET MVC 就是这么一个框架.
有了上面演示的这个例子作为铺垫, 我们应该很容易理解 IoC 所谓的控制反转了. 总的来说, IoC 是我们设计框架所采用的设计思想, 所谓的控制反转即是按照如右图所示的方式将原来实现在应用程序流程控制转移到框架中. 被转移的是一个泛化的可重用的处理流程, 所以 IoC 符合软件设计一个基本的原则, 即重用性.
二, 对流程的定制
我们采用 IoC 实现了流程控制从应用程序向框架自身的反转, 但是这个被反转的仅仅是一个泛化的流程, 任何一个具体的应用都可能需要对组成该流程的某些环节进行定制. 如左图所示, 我们将一个泛化的工作流程 (A=>B=>C) 被定义在框架之中, 建立在该框架的两个应用需要对组成这个流程的某些环节进行定制. 比如步骤 A 和 C 可以被 App1 重用, 但是步骤 B 却需要被定制 (B1),App2 则重用步骤 A 和 B, 但是需要按照自己的方式(C2) 处理步骤 C.
IoC 将对流程的控制从应用程序转移到框架之中, 框架利用一个引擎驱动整个流程的执行. 换句话说, 应用程序无需关心该工作流程的细节, 它只需要启动这个引擎即可. 但是这个引擎一旦被启动, 框架就会完全按照预先编排好的流程进行工作, 如果应用程序希望整个流程按照自己希望的方式被执行, 针对流程的定制一般在发生在启动引擎之前.
一般来说, 框架会以相应的形式提供一系列的扩展点, 应用程序则通过定义扩展的方式实现对流程某个环节的定制. 在引擎被启动之前, 应用程序将所需的扩展注册到框架之中. 一旦引擎被正常启动, 这些注册的扩展会自动参与到整个流程的执行过程中.
虽然应用程序是框架引擎的启动着, 但是一旦引擎被启动之后它就丧失了对流程的控制, 应用程序对流程的定制不是在执行过程中对框架的干预来完成的, 而只需要在流程执行之前就将定制的部分准备好, 框架自身在执行过程中会智能地选择它们. 从这个意义上讲, IoC 对流程的定制遵循着这样一个原则, 即 "Don't call us, we'll call you!", 它被称为好莱坞原则.
综上所述, IoC 一方面通过流程控制从应用程序向框架的反转实现了针对流程自身的重用, 另一方面采用 "好莱坞原则" 使得这个被重用的流程可能自由地被定制, 这两个因素实际上决定了框架自身的价值. 重用让框架不仅仅是为应用程序提供实现单一功能的 API, 而是提供一整套可执行的解决方案; 可定制则使我们可以为不同的应用程序对框架进行定制, 这无疑让框架可以使用到更多的应用之中.
三, IoC 模式
正如我们在上面提到过的, 很多人将 IoC 理解为一种 "面向对象的设计模式", 实际上 IoC 自身不仅与面向对象没有必然的联系, 它也算不上是一种设计模式. 一般来讲, 设计模式提供了一种解决某种具体问题的方案, 但是 IoC 既没有一个针对性的问题领域, 其自身没有提供一种可实施的解决方案, 所以我更加倾向于将 IoC 视为一种设计原则, 实际上很多我们熟悉的设计模式背后采用了 IoC 原则.
模板方法(Template Method)
提到 IoC, 很多人首先想到的是 DI, 但是在我看来与 IoC 联系得最为紧密的则是另一种被称为 "模板方法" 的设计模式. 模板方法模式与 IoC 的意图可以说完全一致, 该模式主张将一个可复用的工作流程或者由多个步骤组成的算法定义成模板方法, 组成这个流程或者算法的步骤则实现在相应的虚方法之中, 模板方法根据流程先后调用这些虚方法. 所有这些方法均定义在同一个类中, 我们可以通过派生该类并重写相应的虚方法达到对流程定制的目的.
对于上面我们演示的这个 MVC 的例子, 我们可以将整个请求处理流程实现在如下一个 MvcEngine 类中, 请求的监听与接收, 目标 Controller 的激活与执行以及 View 的呈现则分别定义在四个受保护的虚方法中, 模板方法 Start 根据预定义的请求处理流程先后调用这四个方法.
- public class MvcEngine
- {
- public void Start(Uri address)
- {
- while (true)
- {
- Request request = this.OnListenAndReceiveRequest(address);
- Task.Run(() =>
- {
- Controller controller = this.OnActivateController(request);
- View view = this.OnExecuteContrller(controller);
- this.OnRenderView(view);
- });
- }
- }
- protected virtual Request OnListenAndReceiveRequest(Uri address) ;
- protected virtual Controller OnActivateController(Request request) ;
- protected virtual View OnExecuteContrller(Controller controller) ;
- protected virtual void OnRenderView(View view) ;
- }
对于具体的应用来说, 如果定义在 MvcEngine 针对请求的处理方式完全符合它的要求, 它只需要创建这个一个 MvcEngine 对象, 然后指定一个对应的基地址调用模板方法 Start 开启这个 MVC 引擎即可. 如果该 MVC 引擎处理请求的某个环节不能满足它的要求, 它可以创建 MvcEngine 的派生类, 并重写实现该环节的相应虚方法即可. 比如说定义在某个应用程序中的 Controller 都是无状态的, 它希望采用单例 (Singleton) 的方式重用已经激活的 Controller 以提高性能, 那么它就可以按照如下的方式创建一个自定义的 FoobarMvcEngine 并按照自己的方式重写 OnActivateController 方法既可.
- public class FoobarMvcEngine : MvcEngine
- {
- protected override View OnExecuteContrller(Controller controller)
- {
- <<省略实现>>
- }
- }
模板方法如果结合 "事件注册" 往往可以使应用程序对流程的定制变得更加自由. 如下面的代码片段所示, 我们为 Controller 的激活与执行以及 View 的呈现定义了六个事件, 它们分别在这个三个环节开始之前和结束之后被触发. 这么一个 MvcEngine 可以直接被使用, 应用程序只需要注册相应的事件完成对请求处理流程的定制.
- public class MvcEngine
- {
- // 其他成员
- protected virtual Controller OnActivateController(Request request) ;
- protected virtual View OnExecuteContrller(Controller controller) ;
- protected virtual void OnRenderView(View view) ;
- public EventHandler<ControllerActivationEventArgs> ControllerActivating;
- public EventHandler<ControllerActivationEventArgs> ControllerActivated;
- public EventHandler<ControllerExecutionEventArgs> ControllerExecuting;
- public EventHandler<ControllerExecutionEventArgs> ControllerExecuted;
- public EventHandler<ViewRenderEventArgs> ViewRendering;
- public EventHandler<ViewRenderEventArgs> ViewRendered;
- }
工厂方法(Factory Method)
对于一个复杂的流程来说, 我们倾向于将组成该流程的各个环节实现在相应独立的组件之中, 那么针对流程的定制就可以通过提供不同组件的形式来实现. 我们知道 23 种设计模式之中有一种重要的类型, 那就是 "创建型模式", 比如常用的 "工厂方法" 和 "抽象工厂", 那么 IoC 所体现的针对流程的共享与定制可以通过它们来完成.
所谓的工厂方法, 说白了就是在某个类中用于提供依赖对象的方法, 这个方法可以是一个单纯的虚方法, 也可以是具有默认实现的虚方法, 至于方法声明的返回类型, 可以是一个接口或者抽象类, 也可以是未被封闭 (Sealed) 的具体类型. 作为它的派生类型, 它可以实现或者重写工厂方法以提供所需的具体对象.
同样以我们的 MVC 框架为例, 我们让独立的对象来完成组成整个请求处理流程的四个核心环节. 具体来说, 我们分别定义了四个核心的类型 (Listener,ControllerActivator,ControllerExecutor 和 ViewGenderer) 来分别负责请求监听与接收, Controller 的激活, Controller 的执行以及 View 的呈现. 这四个对象按照如右图所示的顺序相互协作完成对请求的处理.
如下所示的 Listener,ControllerActivator,ControllerExecutor 和 ViewGenderer 这四个类型的简单定义. 我们没有将它们定义成单纯的抽象类或者接口, 而是未被封闭可以被继承的一般类型, 定义其中的虚方法具有默认的实现. 只有这些默认的实现方式无法满足应用程序具体需求的时候, 我们才需要定义相应的派生类.
- public class Listener
- {
- public virtual Request Listen(Uri address) ;
- }
- public class ControllerActivator
- {
- public virtual Controller ActivateController(Request request) ;
- }
- public class ControllerExecutor
- {
- public virtual View ExecuteController(Controller controller) ;
- }
- public class ViewRenderer
- {
- public virtual void RenderView(View view) ;
- }
在作为 MVC 引擎的 MvcEngine 类中, 我们定义了四个工厂方法 (GetListener,GetControllerActivator,GetControllerExecutor 和 GetViewRenderer) 来提供上述这四种类型的对象. 这四个工厂方法均为具有默认实现的虚方法, 它们默认提供上述四种类型的对象. 在用于启动引擎的 Start 方法中, 我们利用这些工厂方法提供的对象来具体完成请求处理流程的各个核心环节.
- public class MvcEngine
- {
- public void Start(Uri address)
- {
- while (true)
- {
- Request request = this.GetListener().Listen(address);
- Task.Run(() =>
- {
- Controller controller = this.GetControllerActivator()
- .ActivateController(request);
- View view = this.GetControllerExecutor()
- .ExecuteController(controller);
- this.GetViewRenderer().RenderView(view);
- });
- }
- }
- protected virtual Listener GetListener()
- {
- return new Listener();
- }
- protected virtual ControllerActivator GetControllerActivator()
- {
- return new ControllerActivator();
- }
- protected virtual ControllerExecutor GetControllerExecutor()
- {
- return new ControllerExecutor();
- }
- protected virtual ViewRenderer GetViewRenderer()
- {
- return new ViewRenderer();
- }
- }
对于具体的应用程序来说, 如果需要对请求处理的某个环节进行定制, 它需要将定制的操作实现在对应类型 (Listener,ControllerActivator,ControllerExecutor 和 ViewGenderer) 的派生类中. 在 MvcEngine 的派生类中, 我们需要重写对应的工厂方法来提供被定制的对象. 比如上面提及的以单例模式提供目标 Controller 对象的实现就定义在 SingletonControllerActivator 类中, 我们在派生于 MvcEngine 的 FoobarMvcEngine 类中重写了工厂方法 GetControllerActivator 使其返回一个 SingletonControllerActivator 对象.
- public class SingletonControllerActivator : ControllerActivator
- {
- public override Controller ActivateController(Request request)
- {
- <<省略实现>>
- }
- }
- public class FoobarMvcEngine : MvcEngine
- {
- protected override ControllerActivator GetControllerActivator()
- {
- return new SingletonControllerActivator();
- }
- }
上面我们采用工厂方法模式对 MVC 框架进行了重新设计, 右图清晰地展示了该框架以 MvcEngine 为核心的相关组件之间的相互关系, 同时也体现了采用派生 MvcEngine(FoobarMvcEngine)具体的应用是如何通过重写工厂方法 (GetControllerActivator) 对框架实施定制的.
抽象工厂(Abstract Factory)
虽然工厂方法和抽象工厂均提供了一个 "生产" 对象实例的工厂, 但是两者在设计上却有本质的不同. 工厂方法利用定义在某个类型的抽象方法或者虚方法实现了针对单一对象提供方式的抽象, 而抽象工厂在利用一个独立的接口或者类来实现对一组相关对象提供的抽象.
具体来说, 我们需要定义一个独立的工厂接口或者抽象工厂类, 并在其中定义多个的工厂方法来提供 "同一系列" 的多个相关对象. 如果希望抽象工厂具有一组默认的 "产出", 我们也可以将一个未被封闭的具体类作为抽象工厂, 以虚方法形式定义的工厂方法将默认的对象作为返回值. 我们根据实际的需要通过实现工厂接口或者继承抽象工厂类 (不一定是抽象类) 定义具体工厂类来提供一组定制的系列对象.
现在我们采用抽象工厂模式来改造我们的 MVC 框架. 如下面的代码片段所示, 我们定义了一个实例类 EngineFactory 作为创建 MvcEngine 所需四种对象 (Listener,ControllerActivator,ControllerExecutor 和 ViewGenderer) 的抽象工厂, 定义其中的四个工厂方法的返回值体现了工厂默认的产出. 我们在创建 MvcEngine 对象可以提供一个具体的 EngineFactory 对象(如果没有显式指定, MvcEngine 默认使用的是一个自动创建的 EngineFactory 对象). 在用于启动引擎的 Start 方法中, MvcEngine 利用 EngineFactory 来获取相应的对象协作完整对请求的处理流程. 对于我们采用抽象工厂改造后的 MVC 框架, 以 MvcEngine 为核心的相关组件之间的关系体现在如左图所示的 UML 中.
- public class EngineFactory
- {
- public virtual Listener GetListener()
- {
- return new Listener();
- }
- public virtual ControllerActivator GetControllerActivator()
- {
- return new ControllerActivator();
- }
- public virtual ControllerExecutor GetControllerExecutor()
- {
- return new ControllerExecutor();
- }
- public virtual ViewRenderer GetViewRenderer()
- {
- return new ViewRenderer();
- }
- }
- public class MvcEngine
- {
- public EngineFactory Factory { get; private set; }
- public MvcEngine(EngineFactory factory = null)
- {
- this.Factory = factory ?? new EngineFactory();
- }
- public void Start(Uri address)
- {
- while (true)
- {
- Request request = this.Factory.GetListener().Listen(address);
- Task.Run(() =>
- {
- Controller controller = this.Factory.GetControllerActivator()
- .ActivateController(request);
- View view = this.Factory.GetControllerExecutor()
- .ExecuteController(controller);
- this.Factory.GetViewRenderer().RenderView(view);
- });
- }
- }
- }
如果具体的应用程序需要采用上面定义的 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://localhost/mvcapp");
- MvcEngine engine = new MvcEngine(new FoobarEngineFactory());
- Engine.Start(address);
- }
- }
总结
来源: https://www.jb51.net/article/150148.htm