乘风原创程序

  • .NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI)
  • 2020/6/4 10:27:53
  • 依赖倒置原则(DIP)

    依赖倒置(Dependency Inversion Principle,缩写DIP)是面向对象六大基本原则之一。他是指一种特定的的解耦形式,使得高层次的模块不依赖低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象.

    该原则规定:

    • 高层次的模块不应该依赖低层次模块,二者都应该依赖其抽象接口.
    • 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口.

    通过如下一个简单的示例,我们来看一下,我们通过一个简单地下单流程向我们的用户发送相关的短信或者邮件.

    public SendingEmail
    {
     public void Send(string message){
      //do something
     }
    }
    
    public Ordering
    {
     SendingEmail _sendingEmail=null;
     public void Order(string message){
      //Order business operation
      if(_sendingEmail == null)
      {
       _sendingEmail=new SendingEmail();
      }
      _sendingEmail.Send(message);
     }
    }

    这样看我们的代码没问题,目前只要我们完成了订单操作那么,那么则会触发发送功能,但是他却违反了DIP,因为Ordering类依赖于SendingEmail类,而SendingEmail类不是抽象类,而是一个具体的类.那我们再来想一个如果这时候业务口的人过来向我们提出了一个新的需求,要求我们改为短信而不是Email,那么我们需要怎么改?

    public class SendingSMS
    {
     public void Send(string message){
      //do something
     }
    }
    public Ordering
    {
     SendingEmail _sendingEmail=null;
     SendingSMS _sendingSMS=null;
     bool isSendingSMS=true;
     public void Order(string message){
      //Order business operation
      if(isSendingSMS){
       if(_sendingSMS == null)
       {
        _sendingSMS=new SendingSMS();
       }
       _sendingSMS.Send(message);
      }else{
       if(_sendingEmail == null)
       {
        _sendingEmail=new SendingEmail();
       }
       _sendingEmail.Send(message);
      }
      
     }
    }

    根据上述需求我们不得不创建更多的类,并且在Ordering类中声明他,最后我们还需要使用IF ELSE语句来决定使用SMS还是使用电子邮件.但是当我们有更多这种处理操作后,那么可能比现在还混乱,这就意味着我们必须在Ordering类中声明更多新的具体类的实例.

    我们需要抽离出来一种方式,让高级模块去依赖于抽象,用它来代替我们实现类,该抽象将映射到实现类.

    控制反转(IoC)

    控制反转(Inversion of Control,缩写为IOC)是面向对象中的设计原则,他可以帮助我们使高层模块依赖于抽象,而不是底层模块的具体实现.换句话说,他有助于实现(依赖倒置原则——DIP).

    public interface ICustomerCommunication
    {
     void Send(string message);
    }
    然后我们修改SendingEmail和SendingSMS类以从ICustomerCommunication接口继承.
    
    public class SendingEmail:ICustomerCommunication
    {
     public void Send(string message){
      //do something
     }
    }
    
    public class SendingSMS:ICustomerCommunication
    {
     public void Send(string message){
      //do something
     }
    }
    
    

    我们再来修改一下Ordering类以使用该抽象接口

    public Ordering
    {
     ICustomerCommunication _customerComm=null;
     bool isSendingSMS=true;
     public void Order(string message){
      //Order business operation
      if(isSendingSMS){
       if(_customerComm == null)
       {
        _customerComm=new SendingSMS();
       }
       _customerComm.Send(message);
      }else{
       if(_customerComm == null)
       {
        _customerComm=new SendingEmail();
       }
       _customerComm.Send(message);
      }
      
     }
    }

    通过如上修改我们做的控制反转更符合DIP.现在我们的高级模块只需要依赖于抽象,而不用去依赖实现.

    依赖注入(DI)

    依赖注入(Depeondency Injection,缩写为DI)是实现控制反转的一种方式.常用的依赖注入方法有3种:

    • 构造函数注入
    • 方法注入
    • 属性注入

    虽然说通过上面代码我们实现了IoC,并且Ordering类依赖于ICustomerCommunication抽象,但我们仍然在Ordering类中使用了实现类,这使用我们无法在类于类之间完全解耦.

     if(isSendingSMS){
     if(_customerComm == null)
     {
      _customerComm=new SendingSMS();
     }
      _customerComm.Send(message);
     }else{
      if(_customerComm == null)
      {
       _customerComm=new SendingEmail();
      }
      _customerComm.Send(message);
     }
    

    那我们再来说说DI,DI主要帮助我们将实现注入到抽象的类(ICustomerCommunication接口)中.DI的主要减少类之间的耦合,并且将抽象和具体实现的绑定移除依赖类.

    构造函数注入

    通过构造函数注入我们将实现类的对象传递给依赖类的构造函数,并将其分配给这个接口.

    public class Ordering
    {
     ICustomerCommunication _customerComm=null;
     public Ordering(ICustomerCommunication customerComm){
      _customerComm=customerComm;
     }
     public void Order(string message){
      _customerComm.Send(message);
     }
    }
    

    在上面的代码中,构造函数将采用实现类对象绑定到接口中.如果我们将SendingSMS的实现传递给这个类,我们要做的就是声明一个SendingSMS类的实例,然后将其传递给Ordering的构造函数,如下所示:

    方法注入

    通过使用构造函数注入,我们将不得不在Ordering类的生存期内使用实现类的实例SendingSMS或SendingEmail类.现在如果要在每次调用该方法时传递实现类的实例,则必须使用方法注入.

    public class Ordering
    {
     public void Order(ICustomerCommunication customerComm,string message){
      _customerComm=customerComm;
      _customerComm.Send(message);
     }
    }
    
    

    调用方式如下所示

    SendingSMS sendingSMS=new SendingSMS();
    Ordering ordering=new Ordering(sendingSMS);
    ordering.Order(sendingSMS,"msg");
    

    属性注入

    通过如上描述我们知道了构造函数注入方法在整个生命周期中使用依赖类,而方法注入是将我们的注入直接去限于该方法中,然后我们再去了解一下属性注入

    public class Ordering
    {
     public ICustomerCommunication customerComm {get;set;}
     public void Order(string message){
      _customerComm.Send(message);
     }
    }
    

    调用方式如下所示

    SendingSMS sendingSMS=new SendingSMS();
    Ordering ordering=new Ordering(sendingSMS);
    ordering.customerComm=sendingSMS;
    ordering.Order("msg");
    

    其实构造函数注入是实现DI最常用的方法.如果需要在每个方法调用上传递不同的依赖关系,则可以使用方法注入属性注入的使用还是比较少的.

    Reference

    https://zh.wikipedia.org/wiki/控制反转

    https://zh.wikipedia.org/zh-hans/依赖反转原则