依赖倒置原则(Dependence Inversion Principle)
依赖倒置原则是指在设计代码架构时,高层模块不应该依赖底层模块,二者都该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象
- 传统的自顶向下设计
传统设计方式采用自顶向下的原则, 逐级依赖,中层模块和高层模块的耦合度很高,如果需要修改其中的一个模块,则可能会导致其它很多模块也需要修改,牵一发动全身,不易于维护。 不使用依赖反转的系统构架,控制流和依赖关系流的依赖箭头是一个方向的,由高层指向底层,也就是高层依赖底层
- 依赖倒置原则
依赖倒置原则的好处:
- 减少类之间的耦合性,提高系统的稳定性。(根据类与类之间的耦合度从弱到强排列:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系)
- 降低并行开发引起的风险(两个类之间有依赖关系,只有指定两者之间的接口(或抽象类)就可以独立开发了)
- 提高代码的可读性和可维护性
场景示例
假如我们现在简单的装修房子,需要配备一台电视、冰箱、煮饭用的锅
代码如下:
冰箱类(HaierRefrigerator)
class HaierRefrigerator: Refrigerator {
func work() {
print("Make food refresh")
}
}
电视类(XiaoMiTV)
class XiaoMiTV: TV {
func show() {
print("look tv")
}
}
锅类(ZwillingPot)
class ZwillingPot: Pot {
func cook() {
print("Cooking")
}
}
家(Home)
class Home {
var refrigerator: HaierRefrigerator
var tv: XiaoMiTV
var pot: ZwillingPot
init(refrigerator: HaierRefrigerator, tv: XiaoMiTV, pot: ZwillingPot) {
self.refrigerator = refrigerator
self.tv = tv
self.pot = pot
}
func live() {
print("Live in home")
refrigerator.work()
tv.show()
pot.cook()
}
}
测试类(TestHome)
测试类用来组装家
class TestHome {
func test() {
let home = Home(refrigerator: HaierRefrigerator(), tv: XiaoMiTV(), pot: ZwillingPot())
home.live()
}
}
上门代码可以看到组合来一个简单的家,但是组装的TV只可以用xiaomi,冰箱只可以用海尔,锅只可以用双立人,但是实际装修时,并不是这样,有的人喜欢苹果的tv,有的人喜欢WFM的锅,这样对装修的人不友好,装修的用户是可以按照自己的喜好来配置配件。
根据依赖倒置原则进行改进: 代码我们需要修改Home类,让Home类依赖抽象(各个家具配件的接口),而不是依赖于各个组件的具体实现类。
类图如下:
Home类(Home)
class Home {
var refrigerator: Refrigerator
var tv: TV
var pot: Pot
init(refrigerator: HaierRefrigerator, tv: XiaoMiTV, pot: ZwillingPot) {
self.refrigerator = refrigerator
self.tv = tv
self.pot = pot
}
func live() {
print("Live in home")
refrigerator.work()
tv.show()
pot.cook()
}
}
关于依赖倒置、依赖注入、控制反转这二者的区别与联系
- 依赖倒置原则 依赖倒置是一种通用的软件设计原则,主要用来指导框架层面的设计。
高层模块不依赖底层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
- 控制反转 控制反转与依赖倒置有一些相似,它也是一种框架设计常用的模式,但并不是具体的方法。
"控制"指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序猿自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序猿“反转”给了框架。
- 依赖注入 依赖注入是实现控制反转的一个手段,它是一种具体的编程技巧。
我们不通过 new 的方式在类内部创建依赖的对象,而是将依赖的对象在外部创建好之后,通过构造函数等方式传递(或注入)进来,给类使用。 依赖注入真正实现类面向接口编程的愿景,可以很方便地替换同一接口的不同实现,而不会影响到依赖这个接口的客户端