******************依赖倒置原则:最核心的7个字,从外部传入对象(注入),这7个字懂了,这个原则也懂了。***********************

原文url:http://www.cnblogs.com/xiaozhi_5638/p/3613176.html

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

---IoC(控制反转)亦称为 “依赖倒置原理”("Dependency Inversion Principle"),即依赖注入

---核心思想:依赖注入,不要在类中实例化对象然后调用,用注入的方法调入---从外部传入对象(注入接口,接口就是抽象,依赖抽象而不是直接在类中写死对象,类与类之间直接发生关系,依赖具体。),可以通过类的构造参数注入,可以通过类的方法注入,可以通过类的属性注入

      !!!重点!!!外部传入对象只是注入注意依赖倒置原则,不但要依赖注入,还要依赖抽象,依赖抽象才能避免耦合(耦合两个或两个以上的对象紧密的联系,依赖抽象才能应对变化,假如依赖具体了,不能变化了===写死了,两者紧密联系了,就是耦合了。),才能依赖抽象编程,才能算完全遵守依赖倒置原则。具体看这篇文章110行总结一个双向耦合的解耦案例http://www.cnblogs.com/feichengwulai/articles/4151113.html   联系一起记忆,非常好。

  注意,依赖抽象是为了应对变化,如果完全切断耦合后,还能应对变化,那推荐完全切断耦合,比依赖抽象更好,即两个类完全相互不知道,没关系。具体的实现,在客户端完成。这种情况更多是理想化才能完成的,不常用的,更多的应用还是依赖抽象

---重点理解注入两个字:往一个类中注入其他对象的三个方法:类的构造参数,类的方法的参数,类的属性。

  class Manager

  {

    Class_test test_01=new Class_test();   //依赖类对象,最不好

    Interface_test test_02=new Class_test();   //抽象接口对象,比上边好,抽象化了,但还是在类中实例化对象。

    //实例化的缺点实例化就具体了,就具体到谁谁谁了,实例化就产生依赖了。依赖一个从外部传入的对象比依赖一个具体的实例化对象好太多了

    //注意上边都不叫注入

    //下边才叫注入---从外部传入对象(而非在类的内部实例化对象),依赖注入,通过类的构造参数注入

    public Manager(Class_test test_01)   //注入类对象,虽然也是注入,但不是依赖抽象,等于写死了,以后扩展修改起来就不容了。

    {

    }

    public Manager(Interface_test test_02)   //注入接口

    {

    }

  }

---不要恐惧依赖,程序代码中,也不但不可能杜绝依赖,反而很多地方都需要单项依赖。(单项依赖:注意在MVC中Controller是依赖Model的,而Model不依赖Controller。这叫单项依赖 。),上边的注入,就是单项依赖。

 

1,依赖倒置原则:

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

复制代码
 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类应该依赖一个抽象层(接口层),所以原来代码应该这样写:

复制代码
 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();  //调用时,直接实例化对应的类即可,如果想记录到文件即实例化FileLogger类,
如果想发送错误日志邮件,就实例化EmailLogger类。变动时,只需要改动这一个地方即可。
           //对比,上面总结,我们这里不是直接调用某个类,而是根据接口对象,调用继承的类,这样的好处,就是易于扩展和改动。下面的依赖
//注入对这个实例进行了进一步的优化和抽象。

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等等。下图显示依赖倒置发生前后:

 

2,依赖注入:

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

(1)构造注入(Constructor Injection)---(构造传参,对象)即将记录日志的对象作为构造参数传递给Manager对象,从外部传入对象,就彻底不用改动Manager了

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

复制代码
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方法重新定义为:

复制代码
 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这样定义:

复制代码
 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】类型与类型之间不可能完全失去依赖关系,怎样让这种非有不可的依赖关系更微弱,是软件设计的一门高深学问。

 

@重点总结

1,什么叫依赖抽象?(依赖倒置原则,就是要我们依赖抽象,而非依赖实现---细节---具体实例化对象,细节应该依赖于抽象。)

  依赖抽象(C#中即依赖接口),不依赖类或者具体实现。好处是易于类的扩展(不修改类的前提下),遵守了 "开放-封闭"原则(OCP),即对扩展开放,对修改关闭。

2,原则和模式的区别?

  原则和模式是紧密相联的(都是一种思想,好的编程思想,面向对象思想。),后者是前者的总结。模式相对原则,概念性更大,所以模式包含很多原则思想。而原则不能包含模式。

3,什么是抽象?

  抽象就是不确定,计算机逻辑是冰冷且机械死板的,抽象则跟接近人类的思想。例如用文学语句(抽象的语言)去描述一个对象。程序设计七大原则中都有用到抽象!!!

4,高层模块不应该直接依赖于低层模块,高层模块和低层模块应该同时依赖一个抽象层

5,依赖注入,传入继承接口的对象参数,可以传null,然后在类中判断null,如果为null,就不执行即可。对比上边实例记忆。

posted on 2014-03-24 09:41  学到老死  阅读(815)  评论(0编辑  收藏  举报