设计模式-控制反转模式,服务定位器,依赖注入

摘抄自 ASP.NET MVC 5 高级编程

先看一段代码:

    public class EmailService
    {
        public void SendMessage()
        {

        }
    }

    class NotificationSystem
    {
        private EmailService svc;

        public NotificationSystem()
        {
            svc = new EmailService ();
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

在上面的一段代码中,类 NotificationSystem 依赖于类 EmailService,那么类 EmailService就叫做类 NotificationSystem 的依赖。如果一个组件依赖于其它组件,我们称其为耦合。

第一个问题,类 NotificationSystem 的构造函数中创建了一个 EmailService 实例,这就导致了代码的高耦合,就会增加软件修改的负担,因为修改被依赖的类可能破坏依赖于它的类。

第二个问题,在方法 InterestingEventHappened 中,EmailService 实例调用了 SendMessage 方法,但是,如果系统想实现其它功能,也要修改 EmailService 类的实现。

为了解决以上两个问题,也就是降低组件之间的耦合,一般要采用两个独立但相关的步骤:

(1)在组件之间引入抽象层。

  我们可以使用接口来表示两个组件之间的抽象层。那么上面的代码可以改为:

    public interface IMessagingService
    {
        void SendMessage();
    }

    public class EmailService : IMessagingService
    {
        public void SendMessage()
        {

        }
    }

    class NotificationSystem
    {
        private IMessagingService svc;

        public NotificationSystem()
        {
            svc = new EmailService ();
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

在上面的代码中,确保在编写代码时,只调用接口的方法和属性。在类 NotificationSystem 中的私有副本使用一个接口的实例,而不再是具体的类型

(2)把抽象实现的责任转移到消费者类的外部。

  对于上面的代码,也就是把 EmailService 类的实例化转移到 NotificationSystem 类的外部。也就是控制反转。

  Note: 把依赖的创建转移到使用这些依赖的类的外部,这称为控制反转模式,之所以这样命名,是因为反转的是依赖的创建,正因为如此,才消除了消费者类对依赖创建的控制。

  控制反转(IoC)模式是抽象的,它只是表述依赖的创建应该从消费者类中移出,并没有表述如何实现。实现通常有两种方法:服务定位器和依赖注入。

(一)服务定位器

  服务定位模式是控制反转模式的一种实现方式,它通过一个称为服务定位器的外部组件来为需要依赖的组件提供依赖。服务定位器可能是是一个具体的接口,为特定服务提供强类型的请求;也可能是一个泛型类型,可以提供任意类型的请求服务。

1.强类型服务定位器,可能有如下接口

    public interface IServiceLocator
    {
        IMessagingService GetMessagingService();
    }

在需要一个实现了 IMessagingService 接口的对象时,我们知道应该调用 GetMessagingService 就可以返回一个IMessagingService 接口的对象。

那么类 NotificationSystem 可以使用强类型服务定位器重新改写为:

    class NotificationSystem
    {
        private IMessagingService svc;

        public NotificationSystem(IServiceLocator locator)
        {
            svc = locator.GetMessagingService();
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

上面的代码假设创建 NotificationSystem 类的实例的每个人都会访问服务定位器。这样做的好处是,如果应用程序通过服务定位器创建 NotificationSystem 类的实例,那么定位器将自身传递到 NotificationSystem 类的构造函数中;如果是在服务定位器的外部创建 NotificationSystem 类的实例,还需要提供服务定位器到NotificationSystem 类的实现,以便服务定位器找到它的依赖项。

强类型服务定位器的优点:

(1)简单易用。

(2)能够精确的知道能够从服务定位器得到哪些服务。

缺点:

(1)只能够创建在服务定位器中预先设计的服务。

(2)当应用程序中的服务增加时,不得不增加服务定位器中的服务,增加应用程序维护扩展的负担。

2.弱类型服务定位器

如果使用强类型服务定位器的负面影响超过了它所带来的优势,则可以选择使用弱类型服务定位器(weakly-typed service locator),代码如下

    public interface IServiceLocator
    {
        object GetService(Type serviceType);        
    }

这种服务定位器的变体更加灵活,因为它允许请求任意类型的服务。

使用弱类型服务定位器的 NotificationSystem 类的实现代码如下:

    class NotificationSystem
    {
        private IMessagingService svc;

        public NotificationSystem(IServiceLocator locator)
        {
            svc = (IMessagingService)locator.GetService(typeof(IMessagingService ));
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

上面的代码对 GetService 方法返回的结果进行了强制转换,可以使用泛型版本的服务定位器,避免类型的转换,代码如下:

    public interface IServiceLocator
    {
        // 其他服务
        object GetService(Type serviceType);

        // 泛型版本
        TService GetService<TService>();
    }

使用泛型服务定位器的 NotificationSystem 类的实现代码如下:

    class NotificationSystem
    {
        private IMessagingService svc;

        public NotificationSystem(IServiceLocator locator)
        {
            svc = locator.GetService<IMessagingService>();
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

上面的代码避免了类型转换。

但是实现上面的服务定位器接口,必须实现两个几乎相同的方法,而不是只实现一个。可以把其中的泛型方法分割到扩展方法中实现,代码如下:

    public interface IServiceLocator
    {
        object GetService(Type serviceType);
    }

    public static class ServiceLocatorExtensions
    {
        public static TService GetService<TService>(this IServiceLocator locator)
        {
            return (TService)locator.GetService(typeof(TService));
        }
    }

在上面扩展方法中,泛型方法的实现调用了服务定位器中弱类型方法,使用类型转换实现泛型方法。

弱类型服务定位器的优点:

弥补强类型服务定位器的负面影响,减轻维护应用程序的负担。

缺点:

弱类型服务定位器没有提供任何有关可能被请求的服务的类型信息,也没有提供创建自定义服务的简单方法。

总结,服务定位器的优点:

服务定位器的用法比较简单:我们先从某个地方得到服务定位器,然后利用定位器查找依赖。

缺点:

组件需求的不透明性:组件的使用者通过查看构造函数的签名不能知道服务要求的是什么。

(二)依赖注入

依赖注入(Dependency Injection, DI)是另一种控制反转模式的形式,它没有像服务定位器一样的中间件。组件以一种允许依赖的方式来编写,通常由构造函数参数或属性设置器来显式表示。

1.构造函数注入

依赖注入最常见的形式是构造函数注入(constructor injection)。

采用构造函数注入的 NotificationSystem 类的实现代码如下:

    class NotificationSystem
    {
        private IMessagingService svc;

        // 构造函数注入
        public NotificationSystem(IMessagingService service)
        {
            this.svc = service;
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }

优点:

(1)极大的简化了构造函数的实现。

(2)减少了 NotificationSystem 类需要知道的信息。

(3)需求的透明性。

2.属性注入

属性注入(property injection)是一种不太常见的依赖注入方式。顾名思义,该方式是通过设置对象上的公共属性而不是通过使用构造函数参数来注入依赖的。

采用公共属性依赖的 NotificationSystem 类实现代码如下:

    class NotificationSystem
    {
        public IMessagingService svc
        {
            get;
            set;
        }

        public void InterestingEventHappened()
        {
            svc.SendMessage();
        }
    }    

上面代码中 InterestingEventHappened 方法可能会产生异常,如果在调用时没有提供依赖,则会抛出异常,所以可以更改这个方法为:

        public void InterestingEventHappened()
        {
            if (svc == null)
            {
                throw new InvalidOperationException("Please set svc before calling InterestingEventHappened()");
            }
            svc.SendMessage();
        }

选择使用属性注入的理由:

(1)如果依赖在某种意义上是真正可选的,即在消费者类不提供依赖时,也有相应的处理。此时,属性注入可能是一个不错的选择。

(2)类的实例可能需要在我们还没有控制调用的构造函数的情况下被创建。

posted @ 2017-12-17 09:45  sims  阅读(364)  评论(0编辑  收藏  举报