[设计模式之禅读书笔记]003_设计模式六大原则(三):依赖倒置原则(Dependence Inversion Principle)
序言
依赖倒置,这个概念看起来很玄乎,其实很简单。这也是我看所有技术书的心态,在心态上战胜这本书,那么它的内容,也就能很容易理解了。依赖倒置的英文定义如下:
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。
英文不给力的自动跳过吧。这个原则的意思就是,代码要依赖于抽象,而不依赖于实现。这又是什么意思呢?我有个比喻:公司需要你吗?需要,这叫依赖。但是你觉得你所在的公司离开你就不行了吗?显然不是的,公司只是依赖于具有拥有技能的人。所以,这里的拥有技能的人是你和你同事的抽象,当然也包括社会上的人。那所谓的倒置是什么呢?公司如果依赖你,那就叫正置;但是公司现在依赖的是具有技能的人,是抽象集合体,这就叫倒置。所谓正置是指依赖于具体的实现,而倒置则是依赖于对具体的抽象。
正文
1. 依赖倒置有什么好处呢?
依赖倒置原则的要求是依赖于抽象,那么这个问题就相当于在问,基于抽象编程有什么好处呢?这样问题就容易回答了。抽象是什么?抽象是将一群具有相似特征和行为的个体归为一类。这样,我就不用为每一个个体实现的类定制函数了。试想,加入我有一个函数名叫“杀鸡”,它的参数不是抽象的鸡,而是土鸡,那么当我要杀饲料鸡的时候,还需要将“杀鸡”重载一个参数为饲料鸡的函数。这样会十分麻烦。所以,依赖倒置的好处之一是:
不必为某一具体个体定制特别的处理函数,可以避免大量的无趣的函数重载。
那还有其他好处吗?当然有了,依赖倒置可以降低具体类之间的耦合性。这是什么意思呢?看下面一段不好的程序:
1 class QiRuiQQ{ 2 public: 3 void run(){ 4 cout<<"QiRuiQQ running..."<<endl; 5 } 6 }; 7 8 class Driver{ 9 public: 10 void drive(Benz bz){ 11 bz.run(); 12 } 13 }; 14 15 int main(){ 16 17 Driver *d = new Driver(); 18 QiRuiQQ *q = new QiRuiQQ(); 19 d->drive(*q); 20 21 delete q; 22 delete d; 23 24 return 0; 25 }
这两个类的耦合度如何呢?可以告诉你,太高了。为什么呢?试想,假如Driver要开法拉利怎么办?我现在有一个法拉利的类:
1 class Ferrari{ 2 public: 3 void run(){ 4 cout<<"Ferrari running..."<<endl; 5 } 6 };
那Driver只能眼睁睁的看着这样一辆超级跑车,他却开不走。如果要让Driver能开法拉利,我们就需要为Drive加一个重载函数,而这函数体于QiRuiQQ的drive方法惊人的相似。那么我们怎么办呢?这个时候就需要我们伟大的依赖倒置原则了,看符合依赖倒置的代码吧:
1 /** 2 *车辆的抽象 3 **/ 4 class ICar{ 5 public: 6 virtual void run(){} 7 }; 8 /** 9 *司机的抽象 10 **/ 11 class IDriver{ 12 public: 13 virtual void drive(ICar car){} 14 }; 15 /** 16 *车辆的具象:奇瑞QQ 17 **/ 18 class QiRuiQQ:public ICar{ 19 public: 20 void run(){ 21 cout<<"QiRuiQQ running..."<<endl; 22 } 23 }; 24 /** 25 *车辆的具象:法拉利 26 **/ 27 class Ferrari:public ICar{ 28 public: 29 void run(){ 30 cout<<"Ferrari running..."<<endl; 31 } 32 }; 33 /** 34 *司机的具象 35 **/ 36 class Driver:public IDriver{ 37 public: 38 void drive(ICar* car){ 39 car->run(); 40 } 41 }; 42 43 int main(){ 44 45 Driver *d = new Driver(); 46 QiRuiQQ *q = new QiRuiQQ(); 47 d->drive(q); 48 49 delete q; 50 delete d; 51 system("pause"); 52 return 0; 53 }
运行结果:
QiRuiQQ running...
请按任意键继续. . .
此时无论你给司机什么车,司机都会开了,只要那个车继承了ICar。这样就降低了两个类别的耦合度了。所以,依赖倒置的第二个好处就出来了:
降低类间的耦合度。
2. 依赖的实现方法有哪些?
所谓的依赖实现,这个概念貌似很牛逼,其实很简单的,就是怎么把参数传给另一个类。作者给了三种方法,我们来看看到底是哪三种吧。
◇a. 构造函数(构造对象的时候传入另一个对象)
◇b. setter方法(Java里有Bean的概念,C++也可以完全模仿啊!其实就是一个设置成员属性的值的函数而已)
◇c. 接口声明依赖对象(就是我们上面的代码了)
总结
1. 依赖倒置原则可以避免大量的函数重载。
2. 依赖倒置原则可以降低类间的耦合度。
3. 依赖的实现方法有三种,分别是:构造函数、setter方法、接口声明依赖对象。
注意
依赖倒置虽然给我们提供了方便之门,但是从上面的代码也可以看出,它很容易类的规模膨胀,如果为每一个类都声明一个接口,那就会膨胀的可怕。所以,对于我们这些技术人来说,在技术的应用上,要有拿捏,不能没有,也不能全都是。