SOLID面向对象设计原则之IOC控制反转

SOLID 面向对象设计原则之一

S:单一功能,一个类只做一种类型责任
O:开闭原则,对扩展开放,对修改封闭
L:里式替换原则,子类必须能够替换他们的基类
I:接口隔离原则,使用多个专门的接口比使用单一的总接口要好
D:依赖反转
IOC全称是 控制反转;
DI依赖注入是实现IOC的一种方式;

本篇文章,主要介绍IOC

自己从头实现一个的IOC

normal_demo 常规的写法

Database.js文件
有一个变量conStr,一个log方法打印该变量的值
代码如下

1
2
3
4
5
6
7
8
9
10
11
//Database.js

class Database {
constructor(connStr){
this.connStr = connStr;
}
log(){
console.log(this.connStr);
}
}
module.exports = Database;

然后主文件需要使用上面的Database.js文件
没有使用IOC的情况下,常规的方法就是require或者import
这样的问题很显然,当项目变的复杂,依赖关系多的时候,某一个依赖文件更改了目录或者修改了文件名
会影响到很多很多的模块
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
//index.js

const Database = require("../Database.js");
class UserService {
constructor(){
this.db = new Database(Math.random());
}
create() {
return this.db.log();
}
}
const userService = new UserService();
userService.create();

最简单的IOC easy_ioc_demo

把new Database之后的实例直接传到UserService的构造函数中
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Database {
constructor(connStr){
this.connStr = connStr;
}
log(){
console.log(this.connStr);
}
}
class UserService {
constructor(db){
//aop + di
this.db = db;
}
create() {
return this.db.log();
}
}
const db = new Database(Math.random());
// 把db注入到UserService的构造函数中
const userService = new UserService(db);
userService.create();

正规的IOC写法

第一步创建IOC, lib/ioc.js文件

创建一个IOC容器,这里是new 一个Map
分为两种模式,普通模式和单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const { join } = require("path");
module.exports = function createIoC(rootPath) {
return {
container: new Map(),
bind(key, callback) {
this.container.set(key, { callback, singleton: false })
},
singleton(key, callback) {
this.container.set(key, { callback, singleton: true })
},
restore(key){
this.container.delete(key)
},
use(namespace) {
const item = this.container.get(namespace);
if (item) {
// 单例的话,每次都用item.instance
if (item.singleton && !item.instance) {
item.instance = item.callback();
}
return item.singleton ? item.instance : item.callback()
}
return require(join(rootPath,namespace))
}
}
}

第二步,ioc-config.js文件

创建一个ioc容器挂到全局上
引入需要Database.js文件,注册到ioc容器里
然后引入我们的service模块,从ioc容器中拿到需要的依赖,注入到构造函数中即可
关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Database = require("../Database.js");
const createIoC = require("./lib/ioc");
global.ioc = createIoC(__dirname);

// 单例模式,每次输出值相同
ioc.singleton("Database", () => new Database(Math.random()));
// 非单例模式,每次输出值不同
// ioc.bind("Database", () => new Database(Math.random()));

const IocService1 = require("./ioc-service1");
const IocService2 = require("./ioc-service2");

// 把database注入到两个service中,之后文件路径修改,只要在这里改一下就好
// 因为ioc挂到全局了,可以到它的构造函数中use
const userService1 = new IocService1();
userService1.create();
const userService2 = new IocService2(ioc.use("Database"));
userService2.create();

完整代码,见文末的仓库地址哦

目前市面上成熟的前端IOC库 InversifyJS

官方仓库地址
https://github.com/inversify/InversifyJS

使用步骤

第一步:定义接口和types
第二步:使用@injectable 和 @inject装饰器声明依赖
第三步:创建和配置一个容器
第四步:完美解决依赖注入问题
详细内容可以参阅官方仓库和官方文档

Reflect Metadata 简单介绍

核心思想和我上面的demo类似,不过它使用到了Reflect Metadata
简单介绍一下

基础

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只需要:

  • npm i reflect-metadata –save。
  • 在 tsconfig.json 里配置 emitDecoratorMetadata 选项。

Reflect Metadata 的 API 可以用于类或者类的属性上,
Reflect.metadata 当作 Decorator 使用,当修饰类时,在类上添加元数据,当修饰类属性时,在类原型的属性上添加元数据

使用场景

获取类型信息

自定义 metadataKey

控制反转和依赖注入

在 Angular 2+ 的版本中,控制反转与依赖注入便是基于此实现,现在,我们来实现一个简单版

详细内容可以参阅Typescript的文档;

GitHub: GitHub链接
欢迎小伙伴们star 💗❤️💖~~

同时欢迎关注我的个人微信公众号:

分享到:
Disqus 加载中...

如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理