

面试官问我如何理解 IOC 和 DI
source link: https://segmentfault.com/a/1190000040507927
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

IOC/DI
谈到依赖注入,必须先理解 IOC 与 DI。
- IOC,全称 Inversion Of Control,控制反转是面向对象编程的一种设计思想,主要用来降低代码之间的耦合度。
- DI,全称 Dependency Injection,依赖注入是 IOC 的具体实现。是指对象通过外部的注入,避免对象内部自身实现外部依赖的实例化过程。
IOC 控制反转的设计模式可以大幅度地降低了程序的耦合性。而 装饰器在 VSCode 的控制反转设计模式里,其主要作用是实现 DI 依赖注入的功能和精简部分重复的写法。由于该步骤实现较为复杂,我们先从简单的例子为切入点去了解装饰器的基本原理。
Implementation
@serviceA
和 @serviceB
是参数装饰器,用于处理参数,是由 createDecorator
方法创建的。
@参数装饰器使用方法:接收三个参数
- target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链
- key: 方法的名称,注意是方法的名称,而不是参数的名称
- index: 参数在方法中所处的位置的下标
- @返回:返回的值将会被忽略
class C { constructor(@serviceA private a: A, @serviceB private b: B) {} }
所有参数装饰器均由 createDecorator 方法创建,'A'
和 'B'
,均是该装饰器的唯一标识。
const serviceA = createDecorator("A"); const serviceB = createDecorator("B");
装饰器首先判断是否被缓存,如果有被缓存则取出已经缓存好的参数装饰器,如果没被缓存,则创建一个 serviceIdentifier
的参数装饰器。
function createDecorator<T>(serviceId: string): ServiceIdentifier<T> { if (_util.serviceIds.has(serviceId)) { return _util.serviceIds.get(serviceId) as ServiceIdentifier<T>; } }
serviceIdentifier
参数装饰器只做了一件事就是触发 storeServiceDependency
把所有依赖项给存起来,存装饰器本身 id
,参数的下标 index
以及是否可选 optional
。
const id = function serviceIdentifier(target: Ctor<T>, key: string, index: number): void { storeServiceDependency(id, target, index, false); }; id.toString = () => serviceId; _util.serviceIds.set(serviceId, id);
storeServiceDependency
本质是往 target
即 class C
上设置两个静态属性 $di$target
和 $di$dependencies
上面分别存 target
,自身还要再存一次自身 target
是为了判断是否已经存过依赖。
C.$di$target; // class C C.$di$dependencies[0].id.toString(); // A 或者 B C.$di$dependencies; // [{id: serviceIdentifier, index: 1, optional: false}, {id: serviceIdentifier, index: 0, optional: false}]
除了存在类上,还存在了 _util.serviceIds
上。
当类声明的时候,装饰器就会被应用,所以在有类被实例化之前依赖关系就已经确定好了。把 ts
编译就可以证明这点,可以看到 __decorate
在类声明的时候,装饰器就会被执行了,
var C = /** @class */ (function() { function C(a, b) { this.a = a; this.b = b; } C = __decorate([__param(0, serviceA), __param(1, serviceB)], C); return C; })();
紧接着就到了 ServiceCollection
,这里会将装饰器作为 key 唯一标识,实例化的类作为 value,全部存到 svrsCollection
中,svrsCollection
的实现也很简单,直接用 Map
方法存起来。
const aInstance = new A(); const bInstance = new B(); const svrsCollection = new ServiceCollection(); svrsCollection.set(serviceA, aInstance); svrsCollection.set(serviceB, bInstance);
后续只需要使用 get 方法并传入对应的参数装饰器就可以获取对应的实例化好的类了。
svrsCollection.get(serviceA); // new A() svrsCollection.get(serviceB); // new B()
InstantiationService
是实现依赖注入的核心,它是以参数装饰器,例如 serviceA
和 serviceB
等 ServiceIdentifier
为 key
在私有变量 services
中保存所有依赖注入的被实例化好的类。services
保存的是 svrsCollection
。
const instantiationService = new InstantiationService(svrsCollection);
它暴露了三个公开方法:
createInstance
该方法接受一个类以及构造该类的非依赖注入参数,然后创建该类的实例。invokeFunction
该方法接受一个回调函数,该回调函数通过acessor
参数可以访问该InstantiationService
中的所有依赖注入项。createChild
该方法接受一个依赖项集合,并创造一个新的InstantiationService
说明 vscode 的依赖注入机制也是有层次的。
createInstance
方法是实例化的核心方法:
const cInstance = instantiationService.createInstance(C, "L", "R") as C;
首先是获取 ctorOrDescriptor
也就是类 class C
和需要传入非依赖注入的参数 rest
。
const result = this.createCtorInstance(ctorOrDescriptor, rest);
然后使用 getServiceDependencies
把挂载 class C
静态属性的 $di$dependencies
给获取出来并排序,因为存的时候顺序是倒序的
const serviceDependencies = _util .getServiceDependencies(ctor) .sort((a, b) => a.index - b.index);
取出来的依赖项 serviceDependencies
主要是为了遍历并获取里面的参数装饰器 serviceA
和 serviceB
。
const serviceArgs: any[] = []; for (const dependency of serviceDependencies) { const serviceInstance = this.getOrCreateServiceInstance(dependency.id); serviceArgs.push(serviceInstance); }
getOrCreateServiceInstance
本质就是从 services
即 svrsCollection
中获取实例化好的类。
const instanceOrDesc = this.services.get(id); // 相当于 id 即参数装饰器 // svrsCollection.get(id);
当把所有的这些实例化好的类取出来放到 serviceArgs
中后,由于参数装饰器是类实例化的时候就执行完并收集好依赖项,所以 serviceArgs
就是对应 ctor
即 class C
需要注入的依赖参数,合并非依赖参数就能帮助我们成功实例化好我们的 ctor
类。
new ctor(...[...serviceArgs, ...args]);
如果文章和笔记能带您一丝帮助或者启发,请不要吝啬你的赞和收藏,文章同步持续更新,往期文章也收录在 https://github.com/Wscats/art...
欢迎您的关注和交流,你的肯定是我前进的最大动力😁
Recommend
-
50
-
44
前言 上一篇[
-
33
写在前面 最近,有不少读者说看了我的文章后,学到了很多知识,其实我本人听到后是非常开心的,自己写的东西能够为大家带来帮助,确实是一件值得高兴的事情。最近,也有不少小伙伴,看了我的文章后,顺利拿到了大厂Offer,也有...
-
15
面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?思特沃克软件技术(武汉)有限公司 软件工程师“ 本文已经收录进我的 79K Star 的 Java 开源项目 JavaGuide:
-
8
面试官问我:如何设计一个秒杀场景? 前段时间在公众号读者交流群,有读者提问到关于并发场景相关的问题: 从读者的描述,可以看出高并发处理的经验,在面试中占据着举足轻重的地位,关于高并发相关的面试题,一直都是...
-
8
大家好,我是煎鱼。在大家初识 Go 语言时,总会拿其他语言的基本特性来类比 Go 语言,说白了就是老知识和新知识产生关联,实现更高的学习效率。最常见的类...
-
4
老面试官竟问我 Reactor 在 Netty 中是如何实现的 作者:是Yes呀 2022-02-09 09:37:54 开源 这篇其实也算是一个面试点,毕竟 Reactor 也...
-
3
简单理解依赖注入(DI,Dependency injection) 依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。就是这么简单。 简单理解控制反转(IOC,(Inversion of Control) 一开始我们代码依赖关系可能是如图这样的,...
-
11
控制反转与大家熟知的依赖注入同理, 这是通过依赖注入对象的过程. 创建 Bean 后, 依赖的对象由控制反转容器通过构造参数 工厂方法参数或者属性注入. 创建过程相对于普通创建对象的过程是反向, 称之为控制反转 (IoC). Tomcat...
-
5
如何理解Spring框架中的ioc? 如何理解Spring框架中的ioc? ioc,Inversion of...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK