依赖倒置原则(Dependency Inversion Principle)为咱们提供了下降模块间耦合度的一种思路,依赖注入(Dependency Injection)是一种具体的实施方法。html
依赖倒置原则:ide
前面一篇讲软件设计原则的文章中已经提到了“依赖倒置原则”(Dependency Inversion Principle),该原则主要是为了下降模块与模块之间的“耦合度”,提倡模块与模块之间不要发生直接的依赖关系,即:高层模块不该该直接依赖于低层模块,高层模块和低层模块应该同时依赖一个抽象层。若是如今有一个类Manager在处理某一任务时,须要记录错误日志,那么咱们能够这样编写代码:spa
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(); 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等等。下图显示依赖倒置发生先后:code
依赖注入:htm
上面的Manager类虽然再也不直接依赖任何具体的日志记录类型,可是实质上,咱们建立记录日志类对象仍是在Manager内部(catch中),若是咱们想换种方式记录日志,仍是得动Manager类的代码,有没有一种方式,可以让咱们不须要修改Manager代码就能切换日志的记录方式呢?固然是有的,“依赖注入”就是这一问题的具体解决方法,咱们有三种方式去让两个类型发生依赖关系:对象
(1)构造注入(Constructor Injection)blog
在咱们建立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】类型与类型之间不可能彻底失去依赖关系,怎样让这种非有不可的依赖关系更微弱,是软件设计的一门高深学问。
上一篇设计原则(倒数第二小节) http://www.cnblogs.com/xiaozhi_5638/p/3610706.html