Ioc原则对应的几种设计模式

281 阅读6分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路

本文禁止转载,如需转载请注明出处。

Ioc原则对应的几种设计模式

概述

IoC仅仅是一种设计原则,针对IoC的设计原则有多种设计模式,例如:模板方法,简单工厂,抽象工厂等.以下通过这三种设计模式阐述如何实现IoC设计原则的.

模板方法

该模式主张将一个可复用的工作流程或者由多个步骤组成的算法定义成模板方法,组成这个流程或者算法的步骤实现在相应的虚方法之中,模板方法根据按照预先编排的流程去调用这些虚方法。所有这些方法均定义在同一个类中,我们可以通过派生该类并重写相应的虚方法达到对流程定制的目的。

工厂方法

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

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

抽象工厂

然工厂方法和抽象工厂均提供了一个“生产”对象实例的工厂,但是两者在设计上却有本质的不同。工厂方法利用定义在某个类型的抽象方法或者虚方法实现了针对单一对象提供方式的抽象,而抽象工厂则利用一个独立的接口或者抽象类来提供一组相关的对象。

具体来说,我们需要定义一个独立的工厂接口或者抽象工厂类,并在其中定义多个的工厂方法来提供“同一系列”的多个相关对象。如果希望抽象工厂具有一组默认的“产出”,我们也可以将一个未被封闭的具体类作为抽象工厂,以虚方法形式定义的工厂方法将默认的对象作为返回值。我们根据实际的需要通过实现工厂接口或者继承抽象工厂类(不一定是抽象类)定义具体工厂类来提供一组定制的系列对象。

依赖注入(DI容器)

DI:Dependency Injection.含义为依赖注入.DI是一种“对象提供型”的设计模式,在这里我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在一个采用DI的应用中,在定义某个服务类型的时候,我们直接将依赖的服务采用相应的方式注入进来。按照“面向接口编程”的原则,被注入的最好是依赖服务的接口而非实现。

在应用启动的时候,我们会对所需的服务进行全局注册。服务一般都是针对接口进行注册的,服务注册信息的核心目的是为了在后续消费过程中能够根据接口创建或者提供对应的服务实例。按照“好莱坞法则”,应用只需要定义好所需的服务,服务实例的激活和调用则完全交给框架来完成,而框架则会采用一个独立的“容器(Container)”来提供所需的每一个服务实例。 我们将这个被框架用来提供服务的容器称为“DI容器”.

从服务消费的角度来讲,我们借助于一个服务接口对消费的服务进行抽象,那么服务消费程序针对具体服务类型的依赖可以转移到对服务接口的依赖上,但是在运行时提供给消费者总是一个针对某个具体服务类型的对象。不仅如此,要完成定义在服务接口的操作,这个对象可能需要其他相关对象的参与,也就是说提供的这个服务对象可能具有针对其他对象的依赖。作为服务对象提供者的DI容器,在它向消费者提供服务对象之前就会根据服务实现类型和服务注册信息自动创建依赖的服务实例,并将后者注入到当前对象之中。依赖注入主要有三种方式:[构造器注入] [属性注入] [方法注入]

Service Locator

假设我们需要定义一个服务类型Foo,它依赖于另外两个服务Bar和Baz,后者对应的服务接口分别为IBar和IBaz。如果当前应用中具有一个DI容器,那么我们可以采用如下两种方式(ServiceProvider和ServiceLocator)来定义这个服务类型Foo.

public class Foo : IFoo
{
    public IBar Bar { get; }
    public IBaz Baz { get; }
    //方式一:通过构造器注入,通过框架生成服务实例
    public Foo(IBar bar, IBaz baz)
    {
        Bar = bar;
        Baz = baz;
    }  
    public async Task InvokeAsync()
    {
        await Bar.InvokeAsync();
        await Baz.InvokeAsync();
    }
}

public class Foo : IFoo
{
    public Cat Cat { get; }
    public Foo(Cat cat) => Cat = cat; 
    public async Task InvokeAsync()
    {
        //方式二:Service Locator,主动去获取服务实例
        await Cat.GetService<IBar>().InvokeAsync();
        await Cat.GetService<IBaz>().InvokeAsync();
    }
}

以上两种方式虽然都解决了针对服务的解耦问题,但是第二种使用方式不能称之为"依赖注入",而是一种被称为"Service Locator"的设计模式.Service Locator模式同样具有一个通过服务注册创建的全局容器来提供所需的服务实例,该容器被称为"Service Locator".DI容器和Service Locator实际上是同一事物在不同设计模型的不同称谓罢了.DI容器和Service Locator之间的差异体现在 "被谁使用".DI容器的使用者是框架自身,Service Locator的使用者是应用程序.一般情况下我们尽可能通过构造器注入的方式使用,尽量不要使用Service Locator模式.