Reflect,IOC,DI
Posted coderlin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Reflect,IOC,DI相关的知识,希望对你有一定的参考价值。
Reflect
- Reflect对象与proxy对象一样,也是ES6为了操作对象而提供的新的API。
- JS的装饰器更多的是存在于对函数或者属性进行一些操作,比如修改他们的值,代理变量,自动绑定this等功能。
- 但是却无法实现通过反射来获取究竟有哪些装饰器添加到这个类/方法上,Reflect Metadata就是做这个事情的。
Reflect Metadata
- 可以通过装饰器给类添加一些自定义的信息
- 然后通过反射将这些信息提取出来。
import "reflect-metadata";
const target =
//定义元数据
// 给target添加属性Name,值为test, 对比target.name="test",这样只会在对象上面添加源数据,不会改变对象本身
Reflect.defineMetadata('name', 'test', target);
//给target.props上面添加属性name,值为test
Reflect.defineMetadata('name', 'test123', target, 'props');
console.log(Reflect.getOwnMetadata('name',target)); //test
console.log(Reflect.getOwnMetadata('name',target, "props")); // test1234
console.log('target',target);
Reflect.defineMetadata只会在对象上面添加源数据,不会改变对象本身, 对比target.name=“test”,他会对对象本身进行修改。
declator
装饰器。
Reflect.metadata会返回一个装饰器,如
Reflect.metadata 当作 Decorator 装饰器使用,
- 当修饰类时,在类上添加元数据,
- 当修饰类属性时,在类原型的属性上添加元数据
import "reflect-metadata";
function classMetadata(key: string, value: string)
return function (target: any)
Reflect.defineMetadata(key, value, target);
;
function methodMetadata(key: string, value: string)
//方法装饰器的参数是类的prototype和prototypename
return function (target: any, propertyName: string)
Reflect.defineMetadata(key, value, target, propertyName);
;
//修饰类,给Persont添加元数据name,值为person
//@Reflect.metadata("name", "我是装饰Person类的元数据")
@classMetadata("name", "我是装饰Person类的元数据")
class Person
//@Reflect.metadata("name", "method")
@methodMetadata("name", "method")
method(): string
return "work";
console.log(Reflect.getMetadata("name", Person));
console.log(Reflect.getMetadata("name", Person.prototype, "method"));
Reflect.Metadata的本质是Reflect.defineMetadata,装过装饰器接收类或者类的实例。
IOC和DI
例子:
//显示器接口
interface Monitor
// 27寸显示器
class Monitor27inch implements Monitor
// HOST主机接口
interface Host
//联想
class LegendHost implements Host
//电脑类
class Computer
monitor: Monitor;
host: Host;
constructor()
this.monitor = new Monitor27inch();
this.host = new LegendHost();
startUp()
console.log("电脑组装完毕");
const computer = new Computer();
computer.startUp();
如上,定义了电脑类和主机显示器类。但是这样的设计有缺点:
- 无法创建不同的部分组件,如24存显示器
- 需要在类内容手工创建零件或者组件。
// 解决第一个问题
class Computer1
monitor: Monitor;
host: Host;
constructor(monitor: Monitor, host: Host)
this.monitor = monitor;
this.host = host;
startUp()
console.log("电脑组装完毕");
const monitor = new Monitor27inch();
const host = new LegendHost();
const computer1 = new Computer1(monitor, host);
computer1.startUp();
解决了第一个问题。
解决第二个问题,需要手工创建零件或者组件。需要借助IOC
IOC(控制反转)
- IOC是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)
比如上面的例子我们需要自己创建实例。但是IOC的概念中,实例由容器控制。
IOC意味着把对象交给容器,让容器控制,而不是自己控制。
举个例子(纯属举例子): - 结婚需要依赖车子房子,所以需要买车买房,才能构成结婚的条件。
- 你就是服务的使用者,需要知道结婚本身与其依赖的关系是如何构建的,并且需要你手工维护。
如上,就是传统的方式,跟我们上面的案例一样。
使用IOC容器之后:
如 - 系统的服务,同一注册到IOC容器中,如果服务有依赖其他的服务的时候,也需要对依赖进行声明
- 当用户需要使用特定的服务时,IOC容器会负责该服务及其依赖对象的创建与管理工作。
如女朋友注册的时候,需要声明依赖,比如需要房子和车子,就需要进行声明。
当你使用服务的时候,容器会负责创建车子,房子。
但是需要一些代价,就是你会失去控制权。
IOC控制反转
IOC意味着你设计好的对象交给容器恐吓之,而不是使用传统的方式,在对象内部直接控制。
- 以前我们直接在对象内容通过New的方式创建对象,是程序主动创建依赖对象。而现在是由容器来帮忙创建及注入了依赖对象,我们创建对象只是被动的接收依赖对象。
DI
- 对于控制反转来说,其中最常见的就是依赖注入,简称DI。
- 通过依赖注入机制,我们只需要通过简单的配置,而不需要任何代码就可以指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
- 理解DI关键:
- 谁依赖了谁,应用程序依赖IOC容器
- 为什么需要依赖?应用程序需要IOC容器来提供对象需要的外部资源
- 谁注入了谁?IOC容器向应用程序注入依赖的对象。
- 注入了什么?注入了某个对象所需要的外部资源。
- IOC和DI是同一个概念的不同角度描述,依赖注入明确描述了被注入对象依赖。IOC容器配置依赖对象。
以nest.js为案例
nest.js的源码
Module的源码
Controller的源码
可以看到,都是通过Relfec来实现的。
nest.js官网
准备一个module。module是一个容器
// 模块是一个容器
@Module(
//控制器
controllers: [AppController],
)
class AppModule
export default AppModule;
控制器是控制路由
// 控制器一般只接受参数,返回相应,不会处理业务,业务由services服务处理
@Controller("/app")
class AppController
@Get("/hello")
hello()
return "hello";
但是控制器一般只接受参数,返回响应,业务由services服务处理
// 服务需要注册
class AppService
getHello()
return "heelo";
服务需要注册,就跟IOC案例中的,女朋友依赖房子与车子,需要在容器里面注册提供者。
// 模块是一个容器
@Module(
//控制器
controllers: [AppController],
//提供服务的,比如IOC下的案例,车和房子需要在这里注册。
providers: [AppService],
)
这样当控制器在使用的时候,就可以使用该服务
@Controller("/app")
class AppController
constructor(private appService: AppService)
@Get("/hello")
hello()
return this.appService.getHello()
它会在发现该属性的时候,module会创建好这个Appservice服务,然后注入进来,控制器就可以直接使用。只要在constructor声明我需要谁,容器就会自动帮你注入。
服务里使用其他的服务
比如想在一个服务里面使用其他服务,如
// 日志服务
class UseClassLoggerService
log(message: string)
return message
需要在容器里面注册
// 模块是一个容器
@Module(
//控制器
controllers: [AppController],
//提供服务的,比如IOC下的案例,车和房子需要在这里注册。
providers: [AppService,
provide: UseClassLoggerService, //token标识
useClass: UseClassLoggerService,
],
)
然后在使用的服务
import Module, Controller, Get, Injectable from "@nestjs/common";
// 服务需要注册
@Injectable() //injectable表示可注入的
class AppService
constructor(private useClassLoggerService: UseClassLoggerService)
getHello()
return this.useClassLoggerService.log("hello");
Injectable表示可注入的。
解析遇到UseClassLoggerService类型的时候,会拿类UseClassLoggerService作为标识去容器也就是module里面的Providers寻找提供者,刚才在module的时候我们通过UseClassLoggerService注册了一个token。找到之后就用useClass提供的类创建一个实例,注入到AppService
// 其他服务
class OtherService
test()
return "test";
// 模块是一个容器
@Module(
//控制器
controllers: [AppController],
//提供服务的,比如IOC下的案例,车和房子需要在这里注册。
providers: [
AppService,
provide: UseClassLoggerService, //token标识
useClass: UseClassLoggerService, //注册的是一个类
,
provide: OtherService,
useValue: new OtherService(), //注册的是一个实例
,
],
)
useValue注册的就是一个实例。
自定义token
// 模块是一个容器
@Module(
//控制器
controllers: [AppController],
//提供服务的,比如IOC下的案例,车和房子需要在这里注册。
providers: [
AppService,
provide: UseClassLoggerService, //token标识
useClass: UseClassLoggerService, //注册的是一个类
,
provide: 'test',
useValue: new OtherService(), //注册的是一个实例
,
],
)
如上,otherSerce注册的时候,使用自定义的token,那么使用的时候,如果还是
constructor(
private useClassLoggerService: UseClassLoggerService,
private otherSerivces: OtherService
)
就会报错,因为内部会拿OtherService类作为token去容器里面的提供者找,找不到,因为没有对应的token标识。所以这时候需要属性装饰器Inject
constructor(
private useClassLoggerService: UseClassLoggerService,
@Inject('test') private otherSerivces: OtherService
)
这样内部就会通过这个test作为token去容器里面寻找,找到后拿到useValue的实例注入进去。
实现IOC
三个提供者,类提供者依赖value和factory提供者,注册到IOC容器中,通过Map存储, key是token,值就是provider。当用户获取实例的时候,容器就会获取classProvider和依赖,然后注入依赖,创建实例,返回给用户。
- 定义ts类型
interface Type<T>
new (...args: any[]): T;
type Token<T> = Type<T> | InjectionToken;
// 针对字符串的token,类似于nest的Inject,防止字符串重名
class InjectionToken
constructor(public injectionIdentifier: string)
interface BaseProvider<T>
provide: Token<T>;
// 类提供者
interface ClassProvider<T> extends BaseProvider<T>
useClass: Type<T>;
//值提供者
interface ValueProvider<T> extends BaseProvider<T>
useValue: T;
// 工厂提供者
interface FactoryProvider<T> extends BaseProvider<T>
useFactory: () => T;
type Provider<T> = ClassProvider<T> | ValueProvider<T> | FactoryProvider<T>;
提供者有三中,token有两种。
- 实现注册
// 容器
class Container
public providers = new Map<Token<any>, Provider<any>>();
//注册提供者
addProvider<T>(provider: Provider<T>)
// provide就是token
this.providers.set(provider.provide, provider);
例子:
const container = new Container();
const point = x: 10, y: 10 ;
class BasicClass
container.addProvider( provide: BasicClass, useClass: BasicClass );
console.log(container.providers);
值:
Map(1)
[Function: BasicClass] => provide: [Function: BasicClass], useClass: [Function: BasicClass]
以IOC上的案例,
容器改造
// 容器
class Container
public providers = new Map<Token<any>, Provider<any>>();
//注册提供者
addProvider<T>(provider: Provider<T>)
// provide就是token
this.providers.set(provider.provide, provider);
// 注入,根据token创建对应的实例
inject(token: Token<any>)
const provider = this.providers.get(token);
return this.injectWithProvider(token, provider);
getTokenName<T>(token: Token<T>)
// token可能是一个类或者是实例
return token instanceof InjectionToken
? token.injectionIdentifier
: token.name;
injectWithProvider<T>(type: Token<T>, provider: Provider<T>)
if (isClassProvider(provider))
return this.injectClass(provider);
else if (isValueProvider(provider))
return this.injectValue(provider);
else if (isFactoryProvider(provider))
return this.injectFactory(provider);
else
throw new TypeError(`No provider for type $this.getTokenName(type)`);
injectClass<T>(provider: ClassProvider<T>)
// TODO 服务可能依赖于其他服务,比如GirlFriend依赖与House和Car
//return new provider.useClass();
const target = provider.useClass
const params = []
return Reflect.construct(provider.useClass, params, target); //相当于new provider.useClass()
injectValue<T>(provider: ValueProvider<T>)
return provider.useValue;
injectFactory<T>(provider: FactoryProvider<T>)
return provider.useFactory();
可以取出对应的服务。
然后注册三个类
const container = new Container();
class Car // ValueProvider
class House // FactoryProvider
class GirlFrend
// ClassProvider 依赖Car和House
constructor(private car: Car, private house: House)
container.addProvider( provide: House, useFactory: () => new House() );
container.addProvider( provide: Car, useValue: new Car() );
container.addProvider( provide: GirlFrend, useClass: GirlFrend );
const car = container.inject(Car);
const house = container.inject(House);
const girlFreind = container.inject(GirlFrend)
console.log(car);
console.log(house);
console.log('girlFreind',girlFreind);
如上,GirlFried需要依赖House和Car服务,那么在容器在创建GirlFriend这个实例的时候,就需要先创建House和Car的实例,然后传入。如
injectClass<T>(provider: ClassProvider以上是关于Reflect,IOC,DI的主要内容,如果未能解决你的问题,请参考以下文章