3.2 IoC模式
正如前面所提到的,很多人将IoC理解为一种面向对象的设计模式,实际上,IoC不仅与面向对象没有必然的联系,它自身甚至不算是一种设计模式。一般来讲,设计模式提供了一种解决某种具体问题的方案,但 IoC 既没有一个针对性的问题领域,其自身也没有提供一种可操作的解决方案,所以我们更加倾向于将 IoC 视为一种设计原则。很多设计模式都采用了 IoC 原则,下面介绍几种典型的设计模式。
3.2.1 模板方法
提到 IoC,很多人首先想到的是依赖注入,但是笔者认为与 IoC联系最紧密的是一种被称为“模板方法”(Template Method)的设计模式。模板方法设计模式与 IoC 的意图一致,该模式主张将一个可复用的工作流程或者由多个步骤组成的算法定义成模板方法,组成这个流程或者算法的单一步骤则在相应的虚方法之中实现,模板方法根据预先编排的流程调用这些虚方法。这些方法均定义在一个类中,可以通过派生该类并重写相应的虚方法的方式达到对流程定制的目的。
对于前面演示的 MVC 示例,我们可以将整个请求处理流程在一个 MvcEngine 类中实现。如下面的代码片段所示,可以将请求的监听与接收、目标 Controller 的激活与执行、View 的呈现分别定义在 5 个受保护的虚方法中,模板方法 StartAsync 根据预定义的请求处理流程先后调用这5个方法。
对于具体的应用程序来说,如果定义在 MvcEngine 中针对请求的处理方式完全符合要求,那么只需要创建一个 MvcEngine 对象,然后指定一个监听地址来调用模板方法 StartAsync,以开启 MVC 引擎即可。如果该 MVC 引擎对请求某个环节的处理无法满足要求,我们可以创建MvcEngine 的派生类,然后重写实现该环节相应的虚方法即可。例如,定义在某个应用程序中的 Controller 都是无状态的,如果采用单例(Singleton)的方式重用已经激活的 Controller 对象以提高性能,那么可以按照如下方式创建一个自定义的 FoobarMvcEngine,并按照自己的方式重写CreateControllerAsync方法。
3.2.2 工厂方法
对于一个复杂的流程来说,我们倾向于将组成该流程的各个环节实现在相应的组件之中,所以针对流程的定制可以通过提供相应组件的形式实现。23 种设计模式之中有一种重要的类型,即创建型模式,如常用的工厂方法和抽象工厂,IoC 体现的针对流程的共享与定制同样可以通过这些设计模式来完成。
所谓的工厂方法,其实就是在某个类中定义用来提供所需服务对象的方法,这个方法可以是一个单纯的虚方法,也可以是具有默认实现的虚方法,至于方法声明的返回类型,可以是一个接口或者抽象类,也可以是未封闭(Sealed)的具体类型。派生类型可以采用重写工厂方法的方式提供所需的服务对象。
同样,以 MVC 框架为例,我们让独立的组件完成整个请求处理流程的几个核心环节。具体来说,可以为这些核心组件定义如下几个对应的接口:IWebListener接口用来监听、接收和响应请求(针对请求的响应由 ReceiveAsync 方法返回的 HttpContext 上下文来完成);IControllerActivator 接口用于根据当前 HttpContext 上下文激活目标 Controller 对象,并在Controller对象执行后做一些释放回收工作;IControllerExecutor接口和 IViewRenderer接口分别用来完成针对Controller的执行以及针对视图的呈现。
我们在作为 MVC引擎的 MvcEngine中定义了 4个工厂方法(GetWebListener、GetController Activator、GetControllerExecutor和 GetViewRenderer),用来提供上述 4种组件。这 4个工厂方法均为具有默认实现的虚方法,可以利用它们提供默认的组件。在用于启动引擎的 StartAsync方法中,可以利用这些工厂方法提供的对象完成整个请求处理流程。
对于具体的应用程序来说,如果需要对请求处理的某个环节进行定制,那么需要将定制的操作实现在对应接口的实现类中。在 MvcEngine 的派生类中,我们需要重写对应的工厂方法来提供被定制的对象。例如,以单例模式提供目标 Controller 对象的实现就定义在SingletonControllerActivator类中,可以在派生于 MvcEngine的 FoobarMvcEngine类中重写工厂方法GetControllerActivator,使其返回一个SingletonControllerActivator对象。
3.2.3 抽象工厂
虽然工厂方法和抽象工厂均提供了一个“生产”对象实例的工厂,但是两者在设计上有本质上的区别。工厂方法利用定义在某个类型的抽象方法或者虚方法完成了针对“单一对象”的提供,而抽象工厂则利用一个独立的接口或者抽象类提供“一组相关的对象”。
具体来说,我们需要定义一个独立的工厂接口或者抽象工厂类,并在其中定义多个工厂方法来提供“同一系列”的多个相关对象。如果希望抽象工厂具有一组默认的“产出”,也可以将一个未被封闭的类型作为抽象工厂,以虚方法形式定义的工厂方法将默认的对象作为返回值。在具体的应用开发中,可以通过实现工厂接口或者继承抽象工厂类(不一定是抽象类)的方式来定义具体工厂类,并利用它来提供一组定制的对象系列。
可以采用抽象工厂模式改造 MVC 框架。如下面的代码片段所示,可以定义了一个名为IMvcEngineFactory的接口作为抽象工厂,并在其中定义了 4个方法,用来提供请求监听和处理过程使用到的4种核心对象。如果MVC框架提供了针对这4种核心组件的默认实现,就可以按照如下方式为这个抽象工厂提供一个默认实现(MvcEngineFactory)。
可以在创建MvcEngine对象的时候提供一个具体的IMvcEngineFactory对象,如果没有显式指定,MvcEngine 通常默认使用 EngineFactory 对象。在用于启动引擎的 StartAsync 方法中,MvcEngine利用IMvcEngineFactory对象来获取相应的对象完成对请求的处理流程。
如果具体的应用程序需要采用 SingletonControllerActivator 以单例的模式来激活目标Controller 对象,就可以按照如下方式定义一个具体的工厂类 FoobarEngineFactory。最终的应用程序将利用FoobarEngineFactory对象来创建作为引擎的MvcEngine对象。