• 依赖注入


    什么是依赖注入

    依赖注入就是把本来应该在程序中有的依赖在外部注入到程序之中。

    假定有接口A和A的实现B,那么就会执行这一段代码A a=new B();这个时候必然会产生一定的依赖,然而出现接口的就是为了解决依赖的,但是这么做还是会产生耦合,我们就可以使用依赖注入的方式来实现解耦。

    传统方式:

     1 public class PersonAppService
     2     {
     3         private IPersonRepository _personRepository;
     4     
     5         public PersonAppService()
     6         {
     7             _personRepository = new PersonRepository();            
     8         }
     9     
    10         public void CreatePerson(string name, int age)
    11         {
    12             var person = new Person { Name = name, Age = age };
    13             _personRepository.Insert(person);
    14         }
    15     }
    View Code

    PersonAppService使用PersonRepository插入Person到数据库。这段代码的问题:

    • PersonAppService通过IPersonRepository调用CreatePerson方法,所以这方法依赖于IPersonRepository,代替了PersonRepository具体类。但PersonAppService(的构造函数)仍然依赖于PersonRepository。组件应该依赖于接口而不是实现。这就是所谓的依赖性倒置原则。
    • 如果PersonAppService创建PersonRepository本身,它成为依赖IPersonRepository接口的具体实现,不能使用另一个实现。因此,此方式的将接口与实现分离变得毫无意义。硬依赖(hard-dependency)使得代码紧密耦合和较低的可重用。
    • 我们可能需要在未来改变创建PersonRepository的方式。即,我们可能想让它创建为单例(单一共享实例而不是为每个使用创建一个对象)。或者我们可能想要创建多个类实现IPersonRepository并根据条件创建对象。在这种情况下,我们需要修改所有依赖于IPersonRepository的类。
    • 有了这样的依赖,很难(或不可能)对PersonAppService进行单元测试。

    为了克服这些问题,可以使用工厂模式。因此,创建的仓储类是抽象的。看下面的代码:

     1 public class PersonAppService
     2     {
     3         private IPersonRepository _personRepository;
     4     
     5         public PersonAppService()
     6         {
     7             _personRepository = PersonRepositoryFactory.Create();            
     8         }
     9     
    10         public void CreatePerson(string name, int age)
    11         {
    12             var person = new Person { Name = name, Age = age };
    13             _personRepository.Insert(person);
    14         }
    15     }

           PersonRepositoryFactory是一个静态类,创建并返回一个IPersonRepository。这就是所谓的服务定位器模式。以上依赖问题得到解决,因为PersonAppService不需要创建一个IPersonRepository的实现的对象,这个对象取决于PersonRepositoryFactory的Create方法。但是,仍然存在一些问题:

    • 此时,PersonAppService取决于PersonRepositoryFactory。这个已经是比较好的方法,但还是有一个硬依赖(hard-dependency)。
    • 为每个库或每个依赖项乏味的写一个工厂类/方法。
    • 测试性依然不好,由于很难使得PersonAppService使用mock实现IPersonRepository。
    • 解决方案 

      有一些最佳实践(模式)用于类依赖。

      构造函数注入

      重写上面的例子,如下所示:

       1 public class PersonAppService
       2     {
       3         private IPersonRepository _personRepository;
       4     
       5         public PersonAppService(IPersonRepository personRepository)
       6         {
       7             _personRepository = personRepository;
       8         }
       9     
      10         public void CreatePerson(string name, int age)
      11         {
      12             var person = new Person { Name = name, Age = age };
      13             _personRepository.Insert(person);
      14         }
      15     }

      这被称为构造函数注入。现在,PersonAppService不知道哪些类实现IPersonRepository以及如何创建它。谁需要使用PersonAppService,首先创建一个IPersonRepository PersonAppService并将其传递给构造函数,如下所示:

    • var repository = new PersonRepository();
          var personService = new PersonAppService(repository);
          personService.CreatePerson("Yunus Emre"19);

      构造函数注入是一个完美的方法,使一个类独立创建依赖对象。但是,上面的代码有一些问题:

      • 创建一个PersonAppService变得困难。想想如果它有4个依赖,我们必须创建这四个依赖对象,并将它们传递到构造函数PersonAppService。
      • 从属类可能有其他依赖项(在这里,PersonRepository可能有依赖关系)。所以,我们必须创建PersonAppService的所有依赖项,所有依赖项的依赖关系等等. .如此,依赖关系使得我们创建一个对象变得过于复杂了。

      幸运的是,依赖注入框架自动化管理依赖关系。

    • 属性注入

      构造函数注入模式是一个完美的提供类的依赖关系的方式。通过这种方式,您不用创建类的实例,而不会产生依赖项。

      但是,在某些情况下,一些类依赖别的类实现某些非必须的功能(这通常适用于切面如日志记录)。一个类实现了一些业务功能,但是我们还希望他实现日志功能。但是我们的日志实现模型并不确定。在这种情况下,我们可以定义依赖为公共属性,而不是让他们放在构造函数。如果我们想在PersonAppService实现日志功能。我们可以重写类如下:

     1     public class PersonAppService
     2     {
     3         public ILogger Logger { get; set; }
     4     
     5         private IPersonRepository _personRepository;
     6     
     7         public PersonAppService(IPersonRepository personRepository)
     8         {
     9             _personRepository = personRepository;
    10             Logger = NullLogger.Instance;//是一个单例对象,实现了ILogger接口,但实际上什么都没做
    11         }
    12     
    13         public void CreatePerson(string name, int age)
    14         {
    15             Logger.Debug("Inserting a new person to database with name = " + name);
    16             var person = new Person { Name = name, Age = age };
    17             _personRepository.Insert(person);
    18             Logger.Debug("Successfully inserted!");
    19         }
    20     }

    要想实现日志功能,我们只需要为PersonAppService实例设置了Logger,如下面:

       var personService = new PersonAppService(new PersonRepository());
        personService.Logger = new Log4NetLogger();
        personService.CreatePerson("Yunus Emre", 19);

    Log4NetLogger实现ILogger,使得我们的PersonAppService可以写日志。

    如果我们不设置Logger,PersonAppService就不写日志,只是执行了个空函数。因此,我们可以说ILogger实例是PersonAppService 的一个可选的依赖。

    几乎所有的依赖注入框架都支持属性注入模式

    依赖注入框架

    有许多依赖注入框架,都可以自动解决依赖关系。他们可以创建所有依赖项(递归地依赖和依赖关系)。所以你只需要根据注入模式写类和类构造函数&属性,其他的交给DI框架处理!

    目前主流的框架有Autofac、Castle Windsor、Unity,Ninject,StructureMap框架等。

  • 相关阅读:
    散列函数之单散列算法
    NET 使用 RabbitMQ
    leetCode
    Swift 1
    C#并发集合
    多进程与多线程1
    开发编译器
    APUE1
    微服务架构
    ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
  • 原文地址:https://www.cnblogs.com/tianyamoon/p/8297194.html
Copyright © 2020-2023  润新知