依赖倒转原则,就是抽象不应该依赖细节,细节应该依赖于抽象。直白一些就是,要面向接口编程,而不是面向实现编程。
依赖倒转原则有以下两点:
1.高层模块不应该依赖低层模块,两个都应该依赖抽象。
2.抽象不应该依赖细节,细节应该依赖抽象。
那么什么叫高层模块不应该依赖低层模块呢?举个例子,我们一般的程序,都会需要去访问数据库。那么我们编写业务逻辑的部分就是高层模块,而我们编写数据库访问的部分就是低层模块。高层模块的业务逻辑需要依赖低层模块来访问数据库。那么,如果我们要做一个新项目,这些业务逻辑模块是基本没有变化的,但新项目要求使用另一套数据库,但这个时候,高层模块与低层模块耦合在了一起,没有办法进行复用了。而如果我们面向接口编程,高层模块依赖的是一个抽象的接口,而不是某种具体的实现,这样,低层数据库实现的更改就不会影响到高层的业务逻辑了。那么反过来呢?为什么低层模块也要依赖于抽象呢?其实也是为了代码的复用。我们高层的业务逻辑,一般都是比较复杂的,可能会有对多张表进行的复杂操作。如果我们编写低层模块的时候,都针对高层模块的业务逻辑进行特殊实现,那么代码的复用性也不好。一旦去做一个新项目,高层的业务逻辑一发生变化,那么为了配合原来的业务逻辑来进行设计的数据库方法就没法使用了。只有抽象出一套通用的接口,代码的复用性才高。
举例来说,如果不使用面向接口编程,假设一开始数据库是MySQL,高层的业务逻辑模块依赖于具体的MySQL操作类。需要调用插入方法来插入一条数据。那如果我们需要更换成Oracle数据库呢?不止数据库操作类需要修改,业务逻辑模块依赖的部分也要进行修改。那如果我们依赖于抽象进行编程呢?业务逻辑模块依赖于一个抽象的数据库操作接口,它只需要关心这个接口可以完成插入的动作,而不需要关心这种插入具体是MySQL进行的,还是Oracle进行的。那么我们只需要将低层的实现换成Oracle的就可以了,高层的业务逻辑代码完全不需要进行修改。反之也是如此。
里氏代换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类替换成它的子类,程序的行为没有变化。(子类型必须能够替换掉它们的父类型。)只有当子类可以替换掉父类,软件单位的功能不受影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
正是由于里氏代换原则,才使得开放-封闭原则得以成立,我们才可以抽象出各种共通的模块,而不依赖于具体的实现。
依赖倒转原则可以说是面向对象设计的标志,对于程序来说,实现的语言不重要,如果编写的时候考虑的都是如何针对抽象编程而不是针对细节编程,即所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计。反之则是面向过程的设计。