一、什么是依赖(Dependency)
有两个元素A、B,如果元素A的变化会引起元素B的变化,则称元素B依赖(Dependency)于元素A。
在类中,依赖关系有多种表现形式,如:一个类向另一个类发消息;一个类是另一个类的成员;一个类是另一个类的某个操作参数,等等。
二、为什么要依赖注入(DI)
我们先定义四个Class,车,车身,底盘,轮胎。然后初始化这辆车,最后跑这辆车。
我们要改动轮胎类(Tire)把尺寸变成动态的,不是每次都是30。
我们要让整个程序正常运行,我们需要做以下改动:
为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!这样的设计几乎是不可维护的。
三、基本-模式
我们需要进行 控制反转(IoC) ,即上层控制下层,而不是下层控制着上层。我们用 依赖注入(Dependency Injection) 这种方式来实现控制反转。
所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。
这里我们用构造方法传递的依赖注入方式重新写车类的定义:
这里我只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试。
1、基本用法
// step1 首先写一个服务提供者,作为被依赖模块
class LogService {
constructor() { }
public info(...args: any[]): void {
console.info('[INFO]', new Date(), ...args);
}
}
// step2 再编写一个消费者
class CustomerController {
private log!: LogService;
private token = "token_传统写法";
constructor(logInstrance: LogService) {
this.log = logInstrance
}
public main(): void {
this.log.info('Its running...', this.token);
}
}
// step3 传统的调用方式
const logInstance = new LogService()
const customer = new CustomerController(logInstance);
customer.main();
四、托管(类或者类的实例) + 装饰器-模式
1.安装 typescript 环境以及重要的 reflect-metadata,在入口文件引入 reflect-metadata。
2.在 tsconfig.json 中配置 compilerOptions
{
"experimentalDecorators": true, // 开启装饰器
"emitDecoratorMetadata": true, // 开启元编程
}
4.1、Reflect
简介
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API,Reflect 的 API 和 Proxy 的 API 一一对应,
并且可以函数式的实现一些对象操作。另外,使用 reflect-metadata 可以让 Reflect 支持元编程。
重要:
-
Reflect.getMetadata('design:type', target, propertyKey); // 获取被装饰属性的类型
-
Reflect.getMetadata("design:paramtypes",target,propertyKey);// 获取被装饰的参数类型
-
Reflect.getMetadata("design:returntype", target, propertyKey); // 获取被装饰函数的返回值类型
基本使用
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)
Reflect.getMetadata(metadataKey, target, propertyKey)
/**
* metadataKey:meta 数据的 key
* metadataValue:meta 数据的 值
* target:meta 数据附加的目标
* propertyKey(可选):对应的 property key
*/
Reflect.getMetadata('design:type', target, propertyKey); // 获取被装饰属性的类型
Reflect.getMetadata("design:paramtypes", target, propertyKey); // 获取被装饰的参数类型
Reflect.getMetadata("design:returntype", target, propertyKey); // 获取被装饰函数的返回值类型
装饰器简化操作
- 通过 Reflect.defineMetadata 方法调用来添加 元数据
- 通过 @Reflect.metadata 装饰器来添加 元数据
import "reflect-metadata"
@Reflect.metadata("n", 1)
class A {
@Reflect.metadata("n", 2)
public static method1() {
}
@Reflect.metadata("n", 4)
public method2() {
}
}
let obj = new A;
console.log(Reflect.getMetadata("n", A)); // 1
console.log(Reflect.getMetadata("n", A, "method1")) // 2
console.log(Reflect.getMetadata("n", obj)) // undefined
console.log(Reflect.getMetadata("n", obj, "method2")) // 4
自定义类元数据装饰器
import 'reflect-metadata';
function Role(name: string): ClassDecorator {
return target => {
Reflect.defineMetadata('role', name, target);
};
}
@Role('admin')
class Post {}
const metadata = Reflect.getMetadata('role', Post);
console.log(metadata); // 'admin'
4.2、设计思路
- 变更架构设计:
- 1、需要用一个 Map 来存储 注册的依赖,并且它的key必须唯一。所以我们首先设计一个容器
- 2、注册依赖的时候尽可能简单,甚至不需要用户自己定义key,所以这里使用 Symbol 和唯一字符串来确定一个依赖
- 3、我们注册的依赖不一定是类,也可能是一个函数、字符串、单例。暂时不考虑使用装饰器的情况
4.3、代码
step4、先设计一个Container类 用来存储注册的模块,set和get用来注册和读取模块,has用来判断模块是否已经注册
type UnionType = string | symbol;
class Container {
private ContainerMap = new Map<UnionType, any>();
public set = (id: UnionType, value: any): void => {
this.ContainerMap.set(id, value);
}
public get = <T>(id: UnionType): T => {
return this.ContainerMap.get(id) as T;
}
public has = (id: UnionType): Boolean => {
return this.ContainerMap.has(id);
}
}
export const ContainerInstance = new Container();
step5、现在实现Service 装饰器来注册类依赖
- id 是可选的一个标记模块 变量
- singleton是一个可选的标记是否是单例的 变量,
- target表示当前要注册的类,拿到这个类后,给它添加metadata,方便日后使用
type Constructor<T = any> = new (...args: any[]) => T;
export function Service(id: string): Function;
export function Service(singleton: boolean): Function;
export function Service(id: string, singleton: boolean): Function;
export function Service(idOrSingleton?: string | boolean, singleton?: boolean): Function {
return (target: Constructor) => { // 这里的 target 是一个Constructor
let _id;
let _singleton;
let _singleInstance;
if (typeof idOrSingleton === 'boolean') {
console.log(target.name, '🐈--->target.name');
_singleton = true;
_id = Symbol(target.name);
} else {
console.log(target.name, '🍉--->target.name');
// 判断如果设置id, id是否唯一
if (idOrSingleton && ContainerInstance.has(idOrSingleton)) {
throw new Error(`Service: 此标识(${idOrSingleton})已被注册`)
}
_id = idOrSingleton || Symbol(target.name);
_singleton = singleton;
}
Reflect.defineMetadata('cus:id', _id, target); // 目标类上注册 meta数据
if (_singleton) { // 实例 or 不实例化
_singleInstance = new target();
}
ContainerInstance.set(_id, _singleInstance || target); // Map中:meta数据value值为 key;val 为 类实例、或者类
}
}
step6 实现Inject 装饰器用来注入依赖
/**
* 使用id定义模块后,要使用id来注入模块
*/
export function Inject(id?: string): PropertyDecorator {
// 这里的 target 是一个Object, 实例化后的 obj
return (target: Object, propertyKey: UnionType) => {
const Dependency = Reflect.getMetadata("design:type", target, propertyKey); // 获取被装饰属性的类型(目标类)
const _id = id || Reflect.getMetadata("cus:id", Dependency); // 获取目标类的 meta数据value值。
const _dependency = ContainerInstance.get(_id); // 获取实例
// 给属性注入依赖
Reflect.defineProperty(target, propertyKey, { // 给实例挂到属性上。
value: _dependency,
})
}
}
step7、服务提供者 (给类,注册原数据)
ContainerInstance.set('size', 30); // 首次写入size.
@Service('Tire') // 不是单例
class Tire {
// 使用Container.get 注入
static size: number = ContainerInstance.get('size');
static price: number = 100;
public salePrice: number;
constructor() {
Tire.size = ContainerInstance.get('size');
this.salePrice = Tire.price * Tire.size;
}
}
@Service('Bottom', true) // 单例
class Bottom {
constructor() { }
@Inject()
public tire: Tire; // 插入的是一个Tire类,而不是实例
}
@Service('Framework', true) // 单例
class Framework {
constructor() { }
@Inject()
public bottom: Bottom;
}
@Service(true) // 单例
class Car {
constructor() { }
@Inject()
public framework: Framework;
public info(...args: any[]): void {
console.info('[INFO]', new Date(), ...args);
}
}
// 可以在入口文件调用处理
ContainerInstance.set('token', 'token_依赖注入');
// 假设一个消费者
class CarCustomer {
constructor() { }
// 使用Inject注入
@Inject()
private car!: Car
// 使用Container.get 注入
private token = ContainerInstance.get('token');
public main(): void {
this.car.info('Its running...', this.token);
}
public getCarSizeClass(size?: number): void {
const tireConstructor: any = this.car?.framework?.bottom?.tire; // 通过编辑器提示依次拿到下层类
// 使用实例化:多个实例
// if (size) ContainerInstance.set('size', size); // 重新写入size
// return new tireConstructor();
// 使用静态属性:一个类
if (size) tireConstructor.size = size;
return tireConstructor
}
}
const customer2 = new CarCustomer();
customer2.main();
const tireSize30 = customer2.getCarSizeClass();
console.log(tireSize30);
const tireSize50 = customer2.getCarSizeClass(50);
console.log(tireSize50);
4.4、代码执行
使用实例化:多个实例
使用静态属性一个类
五、思考
为什么要依赖注入 + 托管? 上层类可以直接拿到下层类的实例,或拿到下层类,直接做操作。
从而实现:上层对下层类的控制,控制反转。