> For the complete documentation index, see [llms.txt](https://coffe1891.gitbook.io/frontend-hard-mode-interview/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://coffe1891.gitbook.io/frontend-hard-mode-interview/id-6/1.solid-yuan-ze/6.1.5.md).

# 陆.1.5 依赖倒置原则

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

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

```javascript
/**
 * 小羽儿
 */
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(); //>>小明和小羽儿在接吻
```

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

```javascript
/**
 * 小萌
 */
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(); //>>小明和小萌在接吻
```

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

```javascript
/**
 * 大学女生
 */
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`函数来分配女孩)，这叫做**控制反转\*\*(IoC\*\*，\*\*Inversion 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`，这体现了依赖关系的倒置。所以我们可以理解“依赖倒置”为依赖关系被改变。

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

这3者都是为了一个目的：代码更加的\*\*“高内聚,低耦合”。\*\*
{% endhint %}

## 03.何谓高层次，何谓低层次？

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

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

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

## 04.何谓抽象？

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

比如：

1. 这是一位大学女生。大学女生是抽象，具体的小玉儿、小花等就是具体。
2. 这是一幅画。画是抽象，而油画、素描、国画而言就是具体。
3. 这是一件艺术品，艺术品是抽象，而画、照片、瓷器等等就是具体了。
4. 交通工具是抽象，而公交车、单车、火车等就是具体了。
5. 表演是抽象，而唱歌、跳舞、小品等就是具体。

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

## 参考文献

{% hint style="info" %}
[Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle)\
[DIP: The Dependency-Inversion Principle](https://medium.com/@gabriellamedas/dip-the-dependency-inversion-principle-dbe0f784f3aa)\
<https://blog.csdn.net/briblue/article/details/75093382>
{% endhint %}
