【六大设计原则】依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP),其原始定义是:
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.高层模块不应该依赖低层模块,它们都应该依赖其抽象;
2.抽象不应该依赖细节,细节应该依赖抽象。
对于第一点,如果高层模块依赖于低层模块,一旦低层模块发生改变,高层模块也要跟着变动,这会导致开发成本大大提高。抽象一般是不会变化的东西,当模块之间是通过抽象形成依赖后,不管实现的细节怎么变化,只要抽象不变,程序就不需要太大的改动。
对于第二点,抽象一般指的是接口或者抽象类,细节一般指的是实现类,所以就是说:接口或者抽象类不应该依赖于实现类,而实现类应该依赖接口或者抽象类。
绕了半天,依赖倒置原则其实就是我们经常听说的面向接口编程。
依赖倒置原则(或者说面向接口编程)可以减少类之间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和维护性。
先举个不符合依赖倒置原则的简单例子。我们创建一个司机和一个宝马车,然后让司机开宝马车,全程没有抽象类或者接口的参与:
先创建宝马车:
@interface BMWCar : NSObject
- (void)run;
@end
@implementation BMWCar
- (void)run{
NSLog(@"宝马车开动了");
}
@end
再创建司机:
#import "BMWCar.h"
@interface Driver : NSObject
- (void)driveCar:(BMWCar *)car;
@end
@implementation Driver
- (void)driveCar:(BMWCar *)car{
[car run];
}
@end
然后就可以让我们的司机老王开车了:
Driver *wang = [Driver new];
BMWCar *bmw = [BMWCar new];
[wang driveCar:bmw];
这样写的问题在于司机和宝马车死死地耦合了在一起了,我们大款司机只能开宝马,其他车他是没办法开的。这样显然不合理,在真实世界里,只要我们拿了驾驶证,任何牌子的小轿车都是可以开的。合理的做法应该是把车抽象出来,然后让司机依赖车的抽象类:
创建车的抽象类Car
:
@interface Car : NSObject
- (void)run;
@end
@implementation Car
- (void)run{
}
@end
通过继承Car
创建宝马车:
@interface BMWCar : Car
@end
@implementation BMWCar
- (void)run{
NSLog(@"宝马车开动了");
}
@end
创建司机类,让司机和车的抽象类Car
形成依赖:
#import "Car.h"
@interface Driver : NSObject
- (void)driveCar:(Car *)car;
@end
@implementation Driver
- (void)driveCar:(Car *)car{
[car run];
}
@end
好了,下面就重新让我们的老王开车:
Driver *wang = [Driver new];
Car *bmw = [BMWCar new];
[wang driveCar:bmw];
假设又有了一辆新车奔驰:
@interface BenzCar : Car
@end
@implementation BenzCar
- (void)run{
NSLog(@"奔驰车开动了");
}
@end
没问题,我们的老王照样可以开:
Driver *wang = [Driver new];
Car *benz = [BenzCar new];
[wang driveCar:benz];
这就是依赖倒置原则的好处。依赖倒置原则的本质就是通过抽象(接口或者抽象类)使各个类或者模块的实现彼此独立,实现类之间或者模块之间的松耦合。在实战中只要我们遵遁以下的几个规则就可以了:
- 每个类尽量都有接口或抽象类,或者两者具备。抽象是依赖倒置原则的基本要求;
- 变量的表面类型尽量是接口或者抽象类;
- 任何类都尽量从具体类派生。如果一个类是从具体类派生,那么这个类和它的非抽象的父类就形成强耦合;
- 尽量不要覆写基类的方法。如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间的依赖是抽象,覆写了抽象方法,会影响依赖的稳定性;
- 结合里氏替换原则使用。