可能很多小伙伴一听到这个词的第一反应可能都是,这不是后端的概念吗?AOP 啥的,虽然不理解 AOP 是啥,但是就感觉和这个词沾点边。我也是在学了 nestjs
之后,才去研究了这东西,感觉让我对编程有一定的思考,我就想记录一下。我的理解是这样的,大家可以参考一下:
控制反转是一个结果,依赖注入是实现控制反转的一种方式
那这么一说,我们就从 为什么要实现控制反转 和 依赖注入是怎么实现控制反转的 这两个问题来思考,理解控制反转和依赖注入就应该没什么问题了。
关于这个问题,我肯定是因为不反转存在着某些问题呗。我们从代码的角度来看一下,会存在什么问题
// 我们有一个汽车的类
class Car {
// 他有品牌和引擎两个属性
public brand: string;
public engine: Engine;
// 初始化的时候,把引擎也一起初始化了
constructor(brand: string, cylinder: number) {
this.brand = brand;
this.engine = new Engine(cylinder);
}
}
// 引擎类的实现
class Engine {
// 引擎气缸数量
public cylinder: number;
constructor(cylinder: number) {
this.cylinder = cylinder;
}
start() {
console.log('engine start.')
}
stop() {
console.log('engine stop.')
}
}
根据上面的代码,那么我们在初始化 Car
类的时候,我们就要把 Engine
要初始化的参数一起传进来,像这样
const audi = new Car('Audi', 4)
可能有一些编码经验比较丰富的小伙伴一眼就能看出这代码存在的问题,就是我初始化 Car
这个类的时候,不应该把 Engine
初始化需要的参数也带上呀,这样 Car
和 Engine
两个类的耦合性就太高了,如果我的 Engine
构造函数的参数发生了改变,比如多了一些其他参数,那么要改的地方就很多
class Engine {
// 引擎气缸数量
public cylinder: number;
// 新增了 排量 这个属性
public capacity: number
// 初始化的时候也要添加
constructor(cylinder: number, capacity: number) {
this.cylinder = cylinder;
this.capacity = capacity;
}
}
class Car {
// 他有品牌和引擎两个属性
public brand: string;
public engine: Engine;
// 在 car 的构造函数里也要把它加进来
constructor(brand: string, cylinder: number, capacity: number) {
this.brand = brand;
this.engine = new Engine(cylinder, capacity);
}
}
// 在调用的时候也要发生改变
const audi = new Car('Audi', 4, 2.0)
你会发现耦合性很高的代码,一旦需求调整,需要改的地方就很多。这还只是最简单的一层而已,假设项目大了,依赖多了,你也不清楚会有多少层嵌套,可能梳理这个嵌套逻辑都需要很长时间,这样的代码会比较难维护,不熟悉代码的人,很容易就漏改一些地方。
我们希望用怎样的一种方式来处理这种依赖呢?我们希望在使用 Car
的时候,我们就只需要关注 Car
这个类需要属性,Engine
构造函数需要的参数,我不需要管,调用的时候也不需要 Car
去控制,就是把控制 Engine
的方式,交给 Engine
类本身。一起看一下代码怎么改吧
// 引擎类 不需要改动,还是那个类
class Engine {
// ts 的语法可以这样写,让我们的代码更加简洁一些
// 但是这样的话,要记得给属性添加注释,不然会降低代码的可读性
/**
* @param cylinder 气缸数
* @param capacity 排量
*/
constructor(public cylinder: number, public capacity: number) {}
}
class Car {
/**
* @param brand 品牌名称
* @param engine 引擎实例
*/
constructor(public brand: string, public engine: Engine) {}
}
代码这样改后,我们调用的方式也就发生了改变
const engine = new Engine(4, 2.0)
const audi = new Car('Audi', engine)
这时候如果 Engine
类需要发生改变,我们只需要管 Engine
类本身就行了
可能从这个例子来看,看不出来有什么太大的区别。有些人就说了:
要是我
Engine
的构造函数发生改变的时候,new Engine(4, 2.0)
这个代码不也要改吗?
肯定要改呀,构造函数都变了,实例化的参数能不改吗?这是一种编程的思维方式,如果经历过一些比较复杂的项目,你可能就能体会到这种方式的好处,我们可以通过一些目录结构,归档分类的方式,让你很容易就找到需要修改的类,只关注修改对象本身。我平时写前端比较多,都是用 React
,也经历过一些旧的项目,你知道一个 Props
传 5、6 层是什么感觉吗?就是你一直往下找,还可以通过编辑器的跳转功能,帮你往下跳,如果你要往上找,真的可以让你崩溃。所以,怎么管理我们组件的依赖,让依赖耦合性降低,也是我们作为开发者需要关心的问题,也不在于前端还是后端了对吧。