依赖倒置是面向对象设计领域的一种软件设计原则。 有人会有疑惑,设计原则有什么用呢?设计原则是前辈们总结出来的经验,你可以把它们看作是内功心法。只要你在平常开发中按照设计原则进行编码,假以时日,你编程的功力将会大增。
依赖倒置原则的原始定义为:上层模块不应该依赖下层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,其核心思想是:要面向接口编程,不要面向实现编程。
这个是官方的定义,有些人一看可能是懵的!我们该怎么理解上面的定义呢?我们需要咬文嚼字,各个突破。
依赖是一种关系,通俗来讲就是一种需要,人钓鱼?人需要鱼竿钓鱼,因为人就没有鱼竿就钓不了鱼,,所以说人依赖鱼竿,鱼竿被人依赖。在面向对象编程中,代码可以这样编写。
class fishing{
private fishPole:fishPole;
public fishing(){
this.fishPole = new fishPole();
}
}
fishing 的内部持有 fishPole 的引用,这就是依赖在编程世界中的体现。
俗话说,人人平等,但是为什么在同一个公司,你的工资就比你的上级领导工资要少呢?上级领导又比CEO要少呢?(对领导和你来说,那么上级领导就是上层,你就是下层,对于领导和CEO来说,CEO就是上层,领导就是下层),对于任何一个组织机构来说,它是有职能的划分的。按照职能的重要性,自然而然就有了上下之分。模块也是一样,也许某一模块相对于另外一模块它是下层,但是相对于其他模块它又可能是上层。
抽象如其名字一样,是一件很抽象的事物。抽象往往是相对于具体而言的,具体也可以被称为细节,比如: 汽车就是抽象,而宝马520,奔驰E200,就是具体了,抽象可以是物是人也可以是行为!
1. 可以降低类间的耦合性。
2. 可以提高系统的稳定性。
3. 可以减少并行开发引起的风险。
4. 可以提高代码的可读性和可维护性。
使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
1. 每个类尽量提供接口或抽象类,或者两者都具备。
2. 变量的声明类型尽量是接口或者是抽象类。
3. 任何类都不应该从具体类派生。
4. 使用继承时尽量遵循里氏替换原则。
一个非常简单的例子,小明目前是骑车去上学,此例子只是反映小明和车之间的依赖关系!
你可能会这么画类图
//Xiaoming.ts
import { Bike } from "./Bike"
export class xiaoMing {
private bike: Bike
public goSchool() {
this.bike = new Bike();
this.bike.drive();
}
}
//Bike.ts
export class Bike{
public drive(){
console.log("bike drive")
}
}
某天,天空下雨了,小明不能骑自行车了,要做公交车了,然后你改代码
//Xiaoming.ts
import { Bike } from "./Bike"
import { Bus } from "./bus";
export class xiaoMing {
private bike: Bike
private bus: Bus
public goSchool() {
// this.bike = new Bike();
this.bus = new Bus();
this.bus.drive();
}
}
又某天,小明要迟到了赶时间,小明要打的去学校,然后你改代码
//Xiaoming.ts
import { Bike } from "./Bike"
import { Bus } from "./Bus";
import { Taxi } from "./Taxi";
export class xiaoMing {
private bike: Bike;
private bus: Bus;
private taxi: Taxi;
public goSchool() {
// this.bike = new Bike();
// this.bus = new Bus();
this.taxi = new Taxi();
this.taxi.drive();
}
}
这样每次添加一个类,你都要改代码,有没有一种方法能让 xiaoMing 的变动少一点呢?因为这是最基础的演示代码,如果工程大了,代码复杂了,xiaoMing 面对需求变动时改动的地方会更多。而依赖倒置原则正好适用于解决这类情况。先看下类图
我们的代码中,xiaoMing是个具体类,所以它需要一个Person接口, xiaoMing没有依赖抽象,所以我们得引进抽象。而底层的抽象是什么,是 Driveable 这个接口。
//Person.ts
export interface Person {
goSchool();
}
//Driveable.ts
export interface Driveable {
drive();
}
//Bike.ts
import { Driveable } from "./Driveable";
export class Bike implements Driveable{
public drive(){
console.log("bike drive")
}
}
//Bus.ts
import { Driveable } from "./Driveable";
export class Bus implements Driveable{
public drive(){
console.log("bus drive")
}
}
//Taxi.ts
import { Driveable } from "./Driveable";
export class Taxi implements Driveable{
public drive(){
console.log("taxi drive")
}
}
//xiaoMing.ts
import { Driveable } from "./Driveable";
import { Person } from "./Person";
export class xiaoMing implements Person {
private drive;
constructor(drive: Driveable) {
this.drive = drive;
}
public goSchool() {
this.drive.drive();
}
}
//外层调用
let xiaoM = new xiaoMing(new Bus());
xiaoM.goSchool();
xiaoMing类中 drive() 这个方法依赖于 Driveable 接口的抽象,它没有限定自己出行的可能性,任何 Car、Bike 或者是 Bus都可以的。我们可以说是符合了上层不依赖于底层,依赖于抽象的准则了。那么,抽象不应该依赖于细节,细节应该依赖于抽象又是什么意思呢?Driveable 是抽象,它代表一种行为,而 Bike、Car、Bus都是实现细节。如果我改变了实现细节里面的逻辑代码是不会影响到Driveable 抽象,然后我改了Driveable抽象必然会影响到细节的实现,所以说抽象不应该依赖于细节,细节应该依赖于抽象!关系变化如图:
不难发现,上面的依赖箭头发生了改变。所以依赖倒置也由此而来,上层模块xiaoMing不应该依赖下层bike模块,它们都应该依赖于抽象Driveable。依赖倒置我们可以理解为依赖关系被改变,倒置的其实是下层细节,原本它是被上层依赖,现在它倒要依赖与抽象的接口,所以说依赖倒置实质上是面向接口编程的体现。