陆.1.5 依赖倒置原则

01.渣男练成记:控制反转、依赖注入

小明今年20岁上大二,喜欢漂亮女孩,经过长达半年的马拉松式追求,终于俘获了聪明美丽的女孩小羽儿的芳心。两人天天腻歪在一起,一起上自习、吃饭、看电影、甚至kiss……更多的内容少儿不宜。总之,有个女朋友的感觉太好啦,小明已经彻底离不开小羽儿。代码实现如下:

/**
* 小羽儿
*/
class XiaoYuEr {
constructor() {
this.name = "小羽儿";
}
study(){}
eat(){}
watchMovie(){}
kiss(){}
}
/**
* 小明
*/
class XiaoMing {
girlfriend = new XiaoYuEr();
constructor() {
this.name = "小明";
}
study() {
console.log(`${this.name}${this.girlfriend.name}在上自习`);
this.girlfriend.study();
}
eat() {
console.log(`${this.name}${this.girlfriend.name}在吃饭`);
this.girlfriend.eat();
}
watchMovie() {
console.log(`${this.name}${this.girlfriend.name}在看电影`);
this.girlfriend.watchMovie();
}
kiss() {
console.log(`${this.name}${this.girlfriend.name}在接吻`);
this.girlfriend.kiss();
}
}
let xiaoming = new XiaoMing();
xiaoming.study(); //>>小明和小羽儿在上自习
xiaoming.eat(); //>>小明和小羽儿在吃饭
xiaoming.watchMovie(); //>>小明和小羽儿在看电影
xiaoming.kiss(); //>>小明和小羽儿在接吻

幸福的日子持续了一年,小羽儿的父母想送她去美国留学,小明万般不舍,但是无奈贫贱不能移,只能看着女朋友离自己而去。伤心了几个月,好在小明长得酷酷的又有才气,很快有个漂亮的女孩小萌的喜欢上了他。于是……

/**
* 小萌
*/
class XiaoMeng {
constructor() {
this.name = "小萌";
}
study(){}
eat(){}
watchMovie(){}
kiss(){}
}
/**
* 小明
*/
class XiaoMing {
girlfriend = new XiaoMeng(); //因为换了女朋友,小明要在心里把女友换成了小萌,
//里外都要换,代价是昂贵的
constructor() {
this.name = "小明";
}
study() {
console.log(`${this.name}${this.girlfriend.name}在上自习`);
this.girlfriend.study();
}
eat() {
console.log(`${this.name}${this.girlfriend.name}在吃饭`);
this.girlfriend.eat();
}
watchMovie() {
console.log(`${this.name}${this.girlfriend.name}在看电影`);
this.girlfriend.watchMovie();
}
kiss() {
console.log(`${this.name}${this.girlfriend.name}在接吻`);
this.girlfriend.kiss();
}
}
let xiaoming = new XiaoMing();
xiaoming.study(); //>>小明和小萌在上自习
xiaoming.eat(); //>>小明和小萌在吃饭
xiaoming.watchMovie(); //>>小明和小萌在看电影
xiaoming.kiss(); //>>小明和小萌在接吻

有了新女友,小明很快走出了郁郁寡欢的日子。梅开二度看似美好,但其实小明没有那么爱小萌,这段恋情很快就结束了。然后某天,小明突然明白了,原来自己只是害怕孤独,需要有个大学女生陪伴自己上自习、吃饭、看电影……而已,至于具体是哪个女孩他并不特别在意。于是代码如下……

/**
* 大学女生
*/
class CollegeGirl {
constructor() {
this.name = "大学女生";
}
study(){}
eat(){}
watchMovie(){}
kiss(){}
}
class Girl1 extends CollegeGirl {
constructor() {
super();
this.name = "小花";
}
}
class Girl2 extends CollegeGirl {
constructor() {
super();
this.name = "小蓝";
}
}
class Girl3 extends CollegeGirl {
constructor() {
super();
this.name = "小红";
}
}
class Girl4 extends CollegeGirl {
constructor() {
super();
this.name = "小美";
}
}
class Girl5 extends CollegeGirl {
constructor() {
super();
this.name = "小丫";
}
}
class Girl6 extends CollegeGirl {
constructor() {
super();
this.name = "小晴";
}
}
class Girl7 extends CollegeGirl {
constructor() {
super();
this.name = "小珂";
}
}
/**
* 小明
*/
class XiaoMing {
constructor(collegeGirl) {
this.name = "小明";
this.girlfriend = collegeGirl;
}
study() {
console.log(`${this.name}${this.girlfriend.name}在上自习`);
this.girlfriend.study();
}
eat() {
console.log(`${this.name}${this.girlfriend.name}在吃饭`);
this.girlfriend.eat();
}
watchMovie() {
console.log(`${this.name}${this.girlfriend.name}在看电影`);
this.girlfriend.watchMovie();
}
kiss() {
console.log(`${this.name}${this.girlfriend.name}在接吻`);
this.girlfriend.kiss();
}
}
/**
* 小明祈祷老天爷让自己摆脱孤独,老天爷感其诚,每天都安排不同大学女生给他
*/
function showMeCollegeGirl() {
let now = new Date().getUTCDay() % 7;
switch (now) {
case 1:
return new Girl1();
case 2:
return new Girl2();
case 3:
return new Girl3();
case 4:
return new Girl4();
case 5:
return new Girl5();
case 6:
return new Girl6();
case 0:
return new Girl7();
}
}
let xiaoming = new XiaoMing(showMeCollegeGirl());//将依赖的对象注入构造函数
//小明每天和不同女孩一起,过起了没羞没臊的渣男生活
xiaoming.study();
xiaoming.eat();
xiaoming.watchMovie();
xiaoming.kiss();

在编程的世界里,类A引用了某个类B,称为A“依赖”B。

上面这段代码,显示了小明从依赖具体的女孩小羽儿,转为依赖抽象的大学女生。这样之后,小明内心不用更换了女朋友的位置,因此没有心理包袱,感觉很轻松。然后第108行在构造函数里传递(注入)了具体的大学女生,这就叫做依赖(于)注入(DI,Dependency Injection)。并且小明把选择女友的这种“控制权”让渡给了老天爷(由JS引擎全局作用域的showMeCollegeGirl函数来分配女孩),这叫做控制反转(IoCInversion of Control), 通常用好莱坞原则“Don't call me ,I will call you”来比喻。

IoC 最核心的地方就是:在依赖方与被依赖方之间,也就是上文中说的小明与大学女生之间引入了第三方(也即老天爷),这个第三方统称为 IoC 容器。而IoC 容器,也就是实例化抽象类的地方。比如上面的例子,IoC容器老天爷通过生产showMeCollegeGirl函数来实例化多个大学女生。

anyway,这个渣男的例子比较丧 🤣 ,但是又的确是大多数男孩的成长历程,真实得可怕。我们要相信等小明再成熟一点儿,知道自己需要什么样的女孩陪伴终身之后,就会变成暖男的 😇

好了,我们继续接着来讲依赖倒置。

02.依赖倒置

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象,不要依赖于具体实现。简单的说就是要求针对抽象进行编程,不要对具体实现进行编程,这样就降低了与具体实现模块的耦合。

  1. 高层次模块不应该依赖低层次模块,它们都应该依赖于抽象。

  2. 抽象不应该依赖于具体,具体应该依赖于抽象。

前面小明的例子,他从依赖具体的某一个女孩,转为依赖一个更抽象的CollegeGirl类。而其他具体的女孩都继承并依赖抽象的CollegeGirl类。这种依赖关系的大变化,本来其他女孩都是被依赖的,现在反倒要依赖抽象的CollegeGirl,这体现了依赖关系的倒置。所以我们可以理解“依赖倒置”为依赖关系被改变。

“依赖倒置”是粗粒度的软件设计原则; “控制反转”是遵守依赖倒置这个原则而提出来的一种设计模式; 而“依赖注入”属于更细粒度的实现“控制反转”的手段。

这3者都是为了一个目的:代码更加的“高内聚,低耦合”。

03.何谓高层次,何谓低层次?

任何一个组织机构一定有架构的设计有职能的划分。按照职能的重要性,自然就有高低层次之分。并且,随着模块的粒度划分不同高层次与低层次模块会进行变动,也许某一模块相对于另外一模块它是低层次的,但是相对于其他模块它可能又是高层次的。

比如:公司组织架构里面,高层次有CEO、总裁、总经理,低层次依次有部门经理、职员。部门经理相对CEO来说是低层次的,部门经理相对职员来说又是高层次的。

再比如:软件分层架构里面,高层次是UI层,低层次依次有BLL(商业逻辑层),DAL(数据驱动层)。而BLL相对DAL来说是高层次的,BLL相对UI层来说又是低层次的。

04.何谓抽象?

抽象如其名字一样,是一件很抽象的事物。抽象往往是相对于具体而言的,具体也可以被称为细节,当然也被称为具象。

比如:

  1. 这是一位大学女生。大学女生是抽象,具体的小玉儿、小花等就是具体。

  2. 这是一幅画。画是抽象,而油画、素描、国画而言就是具体。

  3. 这是一件艺术品,艺术品是抽象,而画、照片、瓷器等等就是具体了。

  4. 交通工具是抽象,而公交车、单车、火车等就是具体了。

  5. 表演是抽象,而唱歌、跳舞、小品等就是具体。

上面可以知道,抽象可以是物也可以是行为。具体映射到软件开发中,抽象可以是接口或者抽象类形式。

参考文献