首页 > 代码库 > 设计模式六大原则(3)--依赖倒置原则
设计模式六大原则(3)--依赖倒置原则
定义:
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。依赖倒置原则英文全称为Dependence Inversion Principle,简称为DIP。
问题由来:
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。依赖倒置原则的核心就是面向接口编程,理解了面向接口编程也就理解了依赖倒置原则。下面通过一个司机开车的例子简单说明一下:
有一个奔驰车类,奔驰车可以运行,方法为Run,司机类有一个开奔驰车的方法Drive。司机和奔驰车类的代码如下所示:
///<summary>/// 奔驰车类///</summary>public class Benz{//奔驰车运行public void Run(){Console.WriteLine("奔驰车开始运行...");}}///<summary>/// 司机类///</summary>public class Driver{public void Drive(Benz benz){benz.Run();}}
主函数司机小明开动奔驰车,代码如下:
class Client{static void Main(string[] args){//一辆奔驰车var benz = new Benz();//司机小明var xiaoming = new Driver();//小明开奔驰xiaoming.Drive(benz);Console.ReadKey();}}
上面实现了司机开奔驰车的场景,但是对于实际的业务来说,需求是不断变化的。在技术上"变更才显真功夫",只有在"变化"过程中才能知道自己设计或程序是否是松耦合。上面的例子,我们加点要求:司机也要会开宝马车。要完成这个必须先有个宝马车类,如下所示:
///<summary>/// 宝马车类///</summary>public class Bmw{//宝马车运行public void Run(){Console.WriteLine("宝马车开始运行...");}}
尽管有了宝马车类,但是司机根本木有开宝马车的方法。你可能会说,木有那就加上开宝马车的方法!这样,是解决了一时的问题,但是还有法拉利、宾利等车呢?因此,是我们的设计出现了问题:司机类和奔驰车类之间是一个紧耦合的关系,其导致的结果就是系统的可维护性大大降低,可读性降低,两个相似的类需要阅读两个文件,这显然不可取。还有稳定性:固化的、健壮的才是稳定的。这里只是增加了一个车类就需要修改司机类,这不是稳定性,这是易变性。被依赖者的变更竟然让依赖者来承担修改的成本,这样的依赖关系谁肯承担!
在实际项目的开发中,要尽可能减少并行开发引起的风险。并行开发最大的风险就是风险扩散,本来只是一段程序的错误或异常,逐步波及一个功能,一个模块,甚至到最后毁坏了整个项目。因为一个团队,几十人甚至上百人人开发,各人负责不同的功能模块,甲负责汽车类的建造,乙负责司机类的建造,在甲没有完成的情况下,乙是不能完全地编写代码的,缺少汽车类,编译器根本就不会让你通过!在缺少Benz类的情况下,Driver类能编译吗?更不要说是单元测试了!这种相互依存的关系在实际开发中是不被允许的,另一个角度来说这样开发只能挨个进行修改,并且每一项修改可能牵一发而动全身。这种模式 在现在的大中型项目中已经是完全不能胜任了,一个项目是一个团队的协作结果,一个人不可能了解所有的业务和所有的技术,要协作就要并行开发,要并行开发就要解决模块之间的项目依赖关系,然后依赖倒置原则就隆重出场了,呵呵。
依赖倒置原则的核心就是接口,下面我们为车和司机定义两个接口ICar和IDriver,代码如下所示:
///<summary>/// 汽车接口///</summary>public interface ICar{void Run();}///<summary>/// 司机接口///</summary>public interface IDriver{void Drive(ICar car);}
然后让上面定义的司机和汽车(宝马和奔驰)各自继承自己对应的接口,代码如下所示:
///<summary>/// 奔驰车类///</summary>public class Benz:ICar{//奔驰车运行public void Run(){Console.WriteLine("奔驰车开始运行...");}}///<summary>/// 宝马车类///</summary>public class Bmw:ICar{//宝马车运行public void Run(){Console.WriteLine("宝马车开始运行...");}}///<summary>/// 司机类///</summary>public class Driver:IDriver{public void Drive(ICar car){car.Run();}}
此时的类的结构图如下所示:
在开发实现业务需求时,我们应该谨记抽象不依赖细节。在这里,汽车和司机的接口都不依赖细节(具体的实现类),看到这应该对依赖倒置原则理解的差不多了吧。
依赖倒置原则是一个指导思想,是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,为更好使用此原则,在实际项目中我们要按如下方法使用:
- 每一个类都要尽可能有接口或抽象类和两者都有,也不可过分去生搬硬套依赖倒置原则。
- 变量的显示类型尽量是接口或者抽象类。
- 任何类都不要从具体类中派生。
- 尽量不要覆写基类的方法。
- 结合里氏替换原则使用
依赖倒置原则是六个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,大家只要记住是"面向接口编程"就基本上抓住了依赖倒转原则的核心。顺便说一下,实际的项目投产上线和盈利是第一要务,因此设计模式的原则只是提供了指导思想,我们不应该主动去违背,但是限于实际情况不得不违背,否则即便设计得有多么好,架构多么完美,这都是扯淡的事情。一旦超过预期工期或者项目亏本,你老板不高兴,然后你也会不高兴的…