依赖倒置原则
依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
Shubho:这是SOLID原则里最后一个原则。这是海报
它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象
Shubho:考虑一个现实中的例子。你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?
Farhana:是的
Shubho:好,它们没有一个是严格的构建在一个单一单元里;换句话说,它们都是可插拔的,因此当引擎或车轮出问题时,你可以修理它(而不需要修理其它部件),甚至可以换一个。
在替换时,你仅需要确保引擎或车轮符合汽车的设计(如汽车能使用任何1500CC的引擎或任何18寸的车轮)。
当然,汽车也可能允许你在1500CC引擎的地方安装一个2000CC的引擎,事实上对某些制造商(如丰田汽车)是一样的。
现在,如果你的汽车的零部件不具备可插拔性会有什么不同?
Farhana:那会很可怕!因为如果汽车的引擎出故障了,你可能修理整部车或者需要买一个新的。
Shubho:是的,那么该如何做到"可插拔性"呢?
Farhana:这里抽象是关键,对吗?
Shubho:是的,在现实中,汽车是高级模块或实体,它依赖于低级模块或实体,如引擎或车轮。
相比直接依赖于引擎或车轮,汽车应依赖于某些抽象的有规格的引擎或车轮,以便于如果任何引擎或车轮符合抽象,那么它们都能组合到汽车中,汽车也能跑动。
一起看下面的类图
Shubho:注意到上面Car类有两个属性,它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。
Farhana:所以,如果代码中不用依赖倒置,我们将面临如下风险:
使用低级类会破环高级代码;
当低级类变化时需要很多时间和代价来修改高级代码;
产生低复用的代码;
Shubho:你完全掌握了,亲爱的!
1.概念:
依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
2.依赖倒转原则用处:
有些时候为了代码复用,一般会把常用的代码写成函数或类库。这样开发新项目时,直接用就行了。比如做项目时大多要访问数据库,所以我们就把访问数据库的代码写成了函数。每次做项目去调用这些函数。那么我们的问题来了。我们要做新项目时,发现业务逻辑的高层模块都是一样的,但客户却希望使用不同的数据库或存储住处方式,这时就出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起,没办法复用这些高层模块。所以不管是高层模块和低层模块都应该依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个更改都不用担心了。
3.注意事项:
- 高层模块不应该依赖低层模块。两个都应该依赖抽象。
- 抽象不应该依赖结节。细节应该依赖抽象。
4.模拟场景:
场景:
假设现在需要一个Monitor工具,去运行一些已有的APP,自动化来完成我们的工作。Monitor工具需要启动这些已有的APP,并且写下Log。
代码实现1:
namespace TestLibrary.ExtensionsClass { using System; public class AppOne { public bool Start() { Console.WriteLine("1号APP开始启动"); return true; } public bool ExportLog() { Console.WriteLine("1号APP输出日志"); return true; } } public class AppTwo { public bool Start() { Console.WriteLine("2号APP开始启动"); return true; } public bool ExportLog() { Console.WriteLine("2号APP输出日志"); return true; } } public class Monitor { public enum AppNumber { AppOne=1, AppTwo=2 } private AppOne appOne = new AppOne(); private AppTwo appTwo = new AppTwo(); private AppNumber number; public Monitor(AppNumber number) { this.number = number; } public bool StartApp() { return number == AppNumber.AppOne ? appOne.Start() : appTwo.Start(); } public bool ExportAppLog() { return number == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog(); } } }
代码解析1:
在代码实现1中我们已经轻松实现了Monitor去运行已有APP并且写下LOG的需求。并且代码已经上线了.
春...夏...秋...冬...
春...夏...秋...冬...
春...夏...秋...冬...
就这样,三年过去了。
一天客户找上门了,公司业务扩展了,现在需要新加3个APP用Monitor自动化。这样我们就必须得改Monitor。
代码实现2:
namespace TestLibrary.ExtensionsClass { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public class Monitor { public enum AppNumber { AppOne = 1, AppTwo = 2, AppThree = 3, AppFour = 4, AppFive = 5 } private AppOne appOne = new AppOne(); private AppTwo appTwo = new AppTwo(); private AppThree appThree = new AppThree(); private AppFour appFour = new AppFour(); private AppFive appFive = new AppFive(); private AppNumber number; public Monitor(AppNumber number) { this.number = number; } public bool StartApp() { bool result = false; if (number == AppNumber.AppOne) { result = appOne.Start(); } else if (number == AppNumber.AppTwo) { result = appTwo.Start(); } else if (number == AppNumber.AppThree) { result = appThree.Start(); } else if (number == AppNumber.AppFour) { result = appFour.Start(); } else if (number == AppNumber.AppFive) { result = appFive.Start(); } return result; } public bool ExportAppLog() { bool result = false; if (number == AppNumber.AppOne) { result = appOne.ExportLog(); } else if (number == AppNumber.AppTwo) { result = appTwo.ExportLog(); } else if (number == AppNumber.AppThree) { result = appThree.ExportLog(); } else if (number == AppNumber.AppFour) { result = appFour.ExportLog(); } else if (number == AppNumber.AppFive) { result = appFive.ExportLog(); } return result; } } }
代码解析2:
这样会给系统添加新的相互依赖。并且随着时间和需求的推移,会有更多的APP需要用Monitor来监测,这个Monitor工具也会被越来越对的if...else撑爆炸,而且代码随着APP越多,越难维护。最终会导致Monitor走向灭亡(下线)。
介于这种情况,可以用Monitor这个模块来生成其它的程序,使得系统能够用在需要的APP上。OOD给我们提供了一种机制来实现这种“依赖倒置”。
代码实现3:
namespace TestLibrary.ExtensionsClass { using System; public interface IApp { bool Start(); bool ExportLog(); } public class AppOne : IApp { public bool Start() { Console.WriteLine("1号APP开始启动"); return true; } public bool ExportLog() { Console.WriteLine("1号APP输出日志"); return true; } } public class AppTwo : IApp { public bool Start() { Console.WriteLine("2号APP开始启动"); return true; } public bool ExportLog() { Console.WriteLine("2号APP输出日志"); return true; } } public class Monitor { private IApp iapp; public Monitor(IApp iapp) { this.iapp = iapp; } public bool StartApp() { return iapp.Start(); } public bool ExportAppLog() { return iapp.ExportLog(); } } }
代码解析3:
现在Monitor依赖于IApp这个接口,而与具体实现的APP类没有关系,所以无论再怎么添加APP都不会影响到Monitor本身,只需要去添加一个实现IApp接口的APP类就可以了。