• DIP And DI


    依赖倒置(DIP)与依赖注入(DI)

     

      依赖倒置原则(Dependency Inversion Principle)为我们提供了降低模块间耦合度的一种思路,依赖注入(Dependency Injection)是一种具体的实施方法。

    依赖倒置原则:

      前面一篇讲软件设计原则的文章中已经提到了“依赖倒置原则”(Dependency Inversion Principle),该原则主要是为了降低模块与模块之间的“耦合度”,提倡模块与模块之间不要发生直接的依赖关系,即:高层模块不应该直接依赖于低层模块,高层模块和低层模块应该同时依赖一个抽象层。如果现在有一个类Manager在处理某一任务时,需要记录错误日志,那么我们可以这样编写代码:

    class Manager
    {
    //…
    public void DoSomething(ILog logger)
    {
    try
    {
    //…
    }
    catch(Exception ex)
    {
    logger.Log(ex.ToString());
    }
    }
    }

    复制代码
     1 class Manager
     2 {
     3     //
     4     FileLogger _logger;
     5     public void DoSomething()
     6     {
     7         try
     8         {
     9             //…do something
    10         }
    11         catch(Exception ex)
    12         {
    13             if(_logger == null)
    14             {
    15                 _logger = new FileLogger();
    16             }
    17             _logger.Log(ex.ToString())
    18         }
    19     }
    20 }
    21 class FileLogger
    22 {
    23     public void Log(string errorLog)
    24     {
    25         //…write into log file
    26     }
    27 }
    复制代码

    如上代码所示,FileLogger类负责将错误日志保存到文件,Manager类中定义了一个Logger类对象,专门负责记录错误日志,这段代码中的“高层模块”Manager类就直接依赖与“低层模块”FileLogger,如果我们现在需要将错误日志记录通过Email发送给别人,或者发送给别的模块,我们不得不去修改Manager类的代码。

      “依赖倒置原则”建议我们,Manager类不应该直接依赖于FIleLogger类,而应该依赖一个抽象层(接口层),所以原来代码应该这样写:

    class Manager
    {
    private ILog _logger;
    public ILog Logger
    {
    get
    {
    return _logger;
    }
    set
    {
    _logger = value;
    }
    }
    //…
    }

    复制代码
     1 class Manager
     2 {
     3     ILog _logger;
     4     public void DoSomething()
     5     {
     6         try
     7         {
     8             
     9         }
    10         catch(Exception ex)
    11         {
    12             if(_logger == null)
    13             {
    14                 _logger = new FileLogger();
    15                 // _logger = new EmailLogger();
    16                 //_logger = new NotifyLogger();
    17             }
    18             _logger.Log(ex.ToString());
    19         }
    20     }
    21 }
    22 interface ILog
    23 {
    24     void Log(string errorLog);
    25 }
    26 class FileLogger:ILog
    27 {
    28     public void Log(string errorLog)
    29     {
    30         //…write into file
    31     }
    32 }
    33 class EmailLogger:ILog
    34 {
    35     public void Log(string errorLog)
    36     {
    37         //…send to others as email
    38     }
    39 }
    40 class NotifyLogger:ILog
    41 {
    42     public void Log(string errorLog)
    43     {
    44         //… notify other modules
    45     }
    46 }
    复制代码

    如上代码所示,我们把记录错误日志的逻辑抽象出来一个ILog接口,Manager类不再依赖于任何一个具体的类,而是依赖于ILog接口,同时我们可以根据ILog接口实现各种各样的日志记录类,如FileLogger将日志保存到文件、EmailLogger将日志以邮件形式发送给别人、NotifyLogger将错误信息通知程序中其他模块。这样以来,整个代码的灵活度明显增加了,如果我们需要将日志保存到文件,直接使用FileLogger,如果我们想将日志以邮件形式发送别人,直接使用EmailLogger等等。下图显示依赖倒置发生前后:

     

    依赖注入:

      上面的Manager类虽然不再直接依赖任何具体的日志记录类型,但是实质上,我们创建记录日志类对象还是在Manager内部(catch中),如果我们想换种方式记录日志,还是得动Manager类的代码,有没有一种方式,能够让我们不需要修改Manager代码就能切换日志的记录方式呢?当然是有的,“依赖注入”就是这一问题的具体解决方法,我们有三种方式去让两个类型发生依赖关系:

    (1)构造注入(Constructor Injection)

      在我们创建Manager对象的时候,将记录日志的对象作为构造参数传递给新创建的Manager对象,假设Manager有一个带ILog类型参数的构造方法,如:

    class Manager
    {
    //…
    FileLogger _logger;
    public void DoSomething()
    {
    try
    {
    //…do something
    }
    catch(Exception ex)
    {
    if(_logger == null)
    {
    _logger = new FileLogger();
    }
    _logger.Log(ex.ToString())
    }
    }
    }
    class FileLogger
    {
    public void Log(string errorLog)
    {
    //…write into log file
    }
    }

    复制代码
    1 class Manager
    2 {
    3     ILog _logger;
    4     public Manager(ILog logger)
    5     {
    6         _logger = logger;
    7     }
    8     //
    9 }
    复制代码

    那么,我们在创建Manager对象的时候,这样编写代码:

      Manager m = new Manager(new FileLogger());

      //Manager m = new Manager(new EmailLogger());

      //Manager m = new Manager(new NotifyLogger());

    很明显,这种日志记录方式一直不变,对Manager终生有效。

    (2)方法注入(Method Injection)

      为Manager类中每个需要记录日志的方法增加一个ILog的参数,比如Manager.DoSomething方法重新定义为:

    class Manager
    {
    ILog _logger;
    public void DoSomething()
    {
    try
    {

    }
    catch(Exception ex)
    {
    if(_logger == null)
    {
    _logger = new FileLogger();
    // _logger = new EmailLogger();
    //_logger = new NotifyLogger();
    }
    _logger.Log(ex.ToString());
    }
    }
    }
    interface ILog
    {
    void Log(string errorLog);
    }
    class FileLogger:ILog
    {
    public void Log(string errorLog)
    {
    //…write into file
    }
    }
    class EmailLogger:ILog
    {
    public void Log(string errorLog)
    {
    //…send to others as email
    }
    }
    class NotifyLogger:ILog
    {
    public void Log(string errorLog)
    {
    //… notify other modules
    }
    }

    复制代码
     1 class Manager
     2 {
     3     //
     4     public void DoSomething(ILog logger)
     5     {
     6         try
     7         {
     8             //
     9         }
    10         catch(Exception ex)
    11         {
    12             logger.Log(ex.ToString());
    13         }
    14     }
    15 }
    复制代码

    那么我们之后在使用Manager的时候,每次调用方法都应该为它提供一个记录日志的对象,如:

      Manager m = new Manager();

      m.DoSomething(new FileLogger());

      m.DoSomething(new EmailLogger());

      m.DoSomething(new NotifyLogger());

    这种记录日志的方式,只对当前方法有效,每次调用方法都可以不同。

    (3)属性注入(Property Injection)

      在Manager类中公开一个属性,用来设置日志记录对象,Mananger这样定义:

    class Manager
    {
    ILog _logger;
    public Manager(ILog logger)
    {
    _logger = logger;
    }
    //…
    }

    复制代码
     1 class Manager
     2 {
     3     private ILog _logger;
     4     public ILog Logger
     5     {
     6         get
     7         {
     8             return _logger;
     9         }
    10         set
    11         {
    12             _logger = value;
    13         }
    14     }
    15     //
    16 }
    复制代码

    之后我们使用Mananger时,可以随时更换它的日志记录方式:

      Mananger m = new Manager();

      m.Logger = new FileLogger();

      m.Logger = new EmailLogger();

      m.Logger = new NotifyLogger();

    使用这种方式,我们可以随时切换记录日志的方式,它的灵活度介于“构造注入”和“方法注入”之间。

      以上三种依赖注入方法可以混合使用,也就是说,你可以为Manager类定义一个带ILog类型的参数,同时也可以定义一个ILog类型的属性,或者为每个方法增加一个ILog类型的参数。

      注:

        1】在.NET中,“抽象层”可以不使用接口interface去实现,而是直接使用委托,举一个例子,我们使用FileStream.BeginRead方法时,给它提供的一个AsyncCallback回调参数,其实就是属于“方法注入”的一种。

       2】类型与类型之间不可能完全失去依赖关系,怎样让这种非有不可的依赖关系更微弱,是软件设计的一门高深学问。

     

    上一篇设计原则(倒数第二小节)  http://www.cnblogs.com/xiaozhi_5638/p/3610706.html

     
     
     
  • 相关阅读:
    DS博客作业06--图
    DS博客作业05--树
    DS博客作业03--栈和队列
    DS博客作业02--线性表
    DS博客作业01--日期抽象数据类型设计与实现
    C语言博客作业06--结构体&文件
    c语言博客作业05--指针
    C语言博客作业03--函数
    DS博客作业--课程总结
    DS博客作业07--查找
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3614795.html
Copyright © 2020-2023  润新知