这一次,教你从零开始写一个 IoC 容器

218 篇文章 18 订阅

要知道,业界 Java 体系的 Spring Framework 已经成为标配,越是庞大的项目,越是需要 IoC 容器的支持。如果你还不了解什么是依赖注入(DI),什么是 IoC,那么,也没关系,我们这就手把手教你。

首先,我们先理清一下思路,我们不会实现一个非常复杂的东西,也不会出现前三步都是一笔画,而第四步就出现天籁美图,自然也不会教你杀一条龙(那是勇者的工作)。

简单的描述一下需求,我有多个类,只要能够通过一个简单的方法,获取类的实例就可以。

比如,我们只需要能实现下面的代码就算是完成了:

class A {
  b: B;
}

class B {
  c: C;
}

class C {
  hello() {
    console.log('hello world');
  }
}

const container = new Container();
const a = container.get(A);
// a.b.c.hello() === 'hello world'

第一步

我们在上面的例子里看到了 Container 类,首先我们先得知道 Container 需要做什么。

1,2,3,没错,我们觉得它需要分析 A 这个类的属性中依赖了哪些东西,我们一眼就能看出 A 依赖了 B,而 B 依赖了 C,但是程序呢?

为了简化一下,这个地方需要有一个假设,假如我们的属性都是标准的类好了,现在就简单了。

class Container {

  get(Module) {
    // 创建对象
    const obj = new Module();
    // 拿到属性
    const properties = Object.getOwnPropertyNames(obj);
    for(let p of properties) {
      if(!obj[p]) {
        // 如果对象不存在,就往下创建
        if(p === 'b') {
          obj[p] = this.get(B);
        } else if(p === 'c') {
          obj[p] = this.get(C);
        } else {}
      }
    }
  }
}

我们使用了递归的方式来不断的 get 某一个类的实例,碰到如果对象不存在,则不断的进行创建,到这里看起来基本就完成了呢,拍手。

第二步

好在我们只有 3 个类,核心功能这么快就完成了呢,下一步我们来想想怎么优化吧。

如果有另一个类,包裹了其他的几个实例怎么办呢。

class A {
  b: B;
  d: D;
}

class B {
  c: C;
}

class D {
  b: B;
  c: C;
}

按照我们的核心代码,估计就会创建出好几个 B 实例和 C 实例啦。

那么,开始优化吧,我们先加个缓存吧。

class Container {

  cache = {};

  getName(Module) {
    return Module.name.toLowerCase();
  }

  get(Module) {
    // 弄个缓存
    if(this.cache[this.getName(Module)]) {
      return this.cache[this.getName(Module)];
    }

    // 创建对象
    const obj = new Module();
    // 缓存起来下次用
    this.cache[this.getName(Module)] = obj;
    // 拿到属性
    const properties = Object.getOwnPropertyNames(obj);
    for(let p of properties) {
      if(!obj[p]) {
        // 如果对象不存在,就往下创建
        if(p === 'b') {
          obj[p] = this.get(B);
        } else if(p === 'c') {
          obj[p] = this.get(C);
        } else if(p === 'd') {
          obj[p] = this.get(D);
        } else {}
      }
    }
  }
}

经过了缓存,我们可以保证在一次运行中,每个类都只会有一个对象实例存在啦。

好学的第三步

本想就这么结束了,但是好学的同学总是太多,那么再做点微小的工作吧。

我们现在只能创建 A,B,C,D 这四个类,是不是有点不太够?

没关系,我们这就教你怎么样支持任意的类。

聪明的人类想出了一个方法,如果我提前就知道了有哪些类,存在一个表(Map)里,下次用的时候如果名字一样就能匹配上了呢。

Bingo,就是这样。

  init() {
    const fileResults = globby.sync(['**/**.ts', '**/**.js'], {
      cwd: process.cwd(),
      ignore: [
        '**/node_modules/**',
      ],
    });

    for (const name of fileResults) {
      const exports = require(process.cwd() + '/' + name);
      // 把类名和类都存起来
      this.classTable[this.getName(exports)] = exports;
    }
  }

这里使用了一个扫描机制,在执行初始化的时候把类的信息通通放到 classTable 下面,之后我们就能直接使用啦,而且不用关心类名叫什么,把 26 个字母随意组合都可以。

const globby = require('globby');
const path = require('path');

class Container {

  cwd = process.cwd();
  cache = {};
  classTable = {};

  init() {
    const fileResults = globby.sync(['**/**.ts', '**/**.js'], {
      cwd: this.cwd,
      ignore: [
        '**/node_modules/**',
      ],
    });

    for (const name of fileResults) {
      const exports = require(this.cwd + '/' + name);
      // 把类名和类都存起来
      this.classTable[this.getName(exports)] = exports;
    }
  }

  getName(Module) {
    return Module.name.toLowerCase();
  }

  get(Module) {
    // 弄个缓存
    if(this.cache[this.getName(Module)]) {
      return this.cache[this.getName(Module)];
    }

    // 创建对象
    const obj = new Module();
    // 缓存起来下次用
    this.cache[this.getName(Module)] = obj;
    // 拿到属性
    const properties = Object.getOwnPropertyNames(obj);
    for(let p of properties) {
      // 如果对象不存在,就往下创建
      if(!obj[p]) {
        // 如果表里有,就创建
        if(this.classTable[p]) {
          obj[p] = this.get(this.classTable[p]);
        }
      }
    }
  }
}

完成的最后

恭喜大家毕业啦,以后面试你就可以说,我会写 IoC 容器,妥妥的稳过啊。

当然实际的情况会复杂很多,上面的代码只是 injection 包中的核心的精简和抽象,如果遇到复杂场景还是推荐大家直接使用 injection 包,不过想了想应该机会不多,想用 IoC 开发应用,体验依赖注入和应用解耦。直接用我们的  midway 就好了嘛。

老板说了,点下 好看 可以加鸡腿哦。

转自这一次,教你从零开始写一个 IoC 容器

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值