依赖倒置原则
依赖倒置原则(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、抽象不应该依赖细节
3、细节应该依赖抽象
高层模块:通常只策略、业务场景
低层模块:也就是具体实现的细节
抽象:抽象就是指接口或抽象类,两者都是不能直接被实例化的
细节:就是实现类,实现接口或继承抽象类而产生的类就是细节
通俗一点:依赖倒置就是通过抽象(接口或抽象类)使各个类或者模块的实现彼此独立,互不影响,实现模块间的松耦合。
依赖倒置原则在编码中经常被使用,其核心思想就是面向接口编程,而不是面向实现编程。接口是指定义(规范、约束)与实现相分离,它是一种抽象,只要不修改接口声明,那么就可以放心使用,至于接口内部的实现无需关心。所以面向接口编程也可以简单理解为面向协议编程,实现者按照协议来工作。理解了面向接口编程也就理解了依赖倒置。
问题描述
类A直接依赖于类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来实现。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作,假如修改类A,那么会给程序带来不必要的风险。
解决方案
将类A修改为依赖接口P,类B和类C都各自实现接口P,类A通过接口P间接与类B和类C发生联系,这样能够大大降低修改类A的几率。
Demo
师傅开车,我们有一个司机类,司机目前可以开奔驰车,所以我们声明类和方法如下:
// 司机
class Driver {
func drive(_ benZ: BenZ) {
benZ.run()
}
}
// 奔驰
class BenZ {
func run() {
print("benZ start to run...")
}
}
代码非常简单,司机开奔驰车那是开的非常开心,但是这时候司机又想开宝马了,或者司机想要开奥迪了,这该怎么办呢?目前我们的Driver类中的驾驶方法与BenZ类那是紧密耦合啊,司机想开其他车还真难,虽然我们可以为Driver类在添加新的方法来让司机开宝马,但是这样其实是不友好的,重复代码太多,明明就是开车的功能,写这么多方法,显然不对,为了解决这个问题,我们必须去除Driver类对奔驰类的依赖,那么就该使用DIP原则
1、声明汽车协议
// 声明汽车协议
protocol CarProtocol {
func run()
}
2、让奔驰、宝马都遵循开汽车协议
// 奔驰
class BenZ: CarProtocol {
func run() {
print("benZ start to run...")
}
}
// 宝马
class BMW: CarProtocol {
func run() {
print("bmw start to run...")
}
}
3、司机只需要暴露驾驶方法即可,依赖协议进行开车
// 司机
class Driver {
func drive(_ car: CarProtocol) {
car.run()
}
}
现在司机想开什么车子就开什么车子,如果想开奥迪,创建一个奥迪类,遵守开汽车协议,那么就OK了
let driver = Driver()
// 开奔驰
driver.drive(BenZ())
// 开宝马
driver.drive(BMW())
如果想进一步优化,其实driver方法也应该声明为接口,即协议方法,但是个人感觉目前其实OK了,符合我们所讲的高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象
依赖倒置原则优点
1、可以减少类间的耦合性
2、提高系统的稳定性
3、降低并行开发引起的风险
4、提高代码的可读性和可维护性
依赖注入
依赖是可以传递的,A对象依赖B对象,B对象又依赖C对象,C对象又依赖D。。。生生不息,依赖不止,但是记住一点:只要做到抽象依赖,即使是多层的依赖传递也是无所谓的。
依赖注入的方式,更多详细细节看这里
1、构造函数注入
class Driver {
let car: CarProtocol
init(_ car: CarProtocol) {
self.car = car
}
func drive() {
car.run()
}
}
2、属性注入
class Driver {
var car: CarProtocol?
func drive() {
car?.run()
}
}
3、方法注入
class Driver {
func drive(_ car: CarProtocol) {
car.run()
}
}
参考
快速理解 设计模式六大原则
设计模式六大原则(3):依赖倒置原则