IoC:Inversion of Control,字面的意思是控制反转,它是一种程序设计的思路。Container 不装在哪出现,一般它都指的就是容器,只不过在什么地方出现,容器里装的东西都不太一样。下面了解一下 Node.js 应用框架 Adonis.js 里的 IoC 容器。
问题
无用抽象
先来看个程序设计问题,比如在应用里要确定数据库只被连接一次,可以把配置数据库用的代码单独放在一个文件里,然后在应用的其它地方导入这个文件里包含的东西。这段代码类似下面这样:
lib/database.js
const knex = require('knex') const connection = knex({ client: 'mysql', connection: {} }) module.exports = connection
Knex.js 是一个在 Node 应用上用的 SQL 查询构建器。上面代码就是用了这个 Package,配置一下,创建了一个对 MySQL 类型的数据的链接,然后导出了创建的这个 connection。这样在应用里,想要使用数据库连接,你就需要导入这个 database.js 。
依赖管理
依赖管理也是大型项目面临的问题。因为依赖之间互相都不知道彼此,所以我们用它们的时候,需要自己把它们组合到一块儿。
比如在 redis 数据库中存储应用的 session,代码类似下面这样:
class Session { constructor (redis) { // needs redis } } class Redis { constructor (config) { // needs config } } class Config { constructor (configDirectory) { // needs config directory } }
Session 类需要用到 Redis 类,因为我们得用 Redis 数据库存储 session。Redis 类又需要 Config 类,因为在使用 Redis 数据库的时候需要做一些配置,这样配置可能存储在某个文件里,使用 Config 类可以从配置文件里得到指定的配置。这样我们在使用 Session 类的时候,需要做下面这些事情:
const config = new Config(configDirectory) const redis = new Redis(config) const session = new Session(redis)
新建 Config 实例,再新建 Redis 实例,然后把 Config 实例交给 Redis 实例使用,再新建一个 Session 实例,把需要的并且配置好的 Redis 实例交给 Session 实例去使用。
难测试
如果不用 IoC 容器,你需要用不同的方法去 mock(假装) 依赖,或者使用第三方库,比如 sinonjs 。使用了 IoC 容器,创建这些 fakes 就很简单了,因为所有的依赖都是在 IoC 容器里面解决的。
绑定依赖
接着上面遇到的问题,现在假设我们要在 IoC 容器里绑定 Redis 库,要确定它知道怎么组合它自己。
首先做的事就是去创建真正的实施并且在 constructor 参数里定义需要的所有依赖的东西。
class Redis { constructor (Config) { const redisConfig = Config.get('redis') // connect to redis server } } module.exports = Redis
Redis 数据库需要做一些配置,相关的配置存储在某个地方,Config 这个对象可以得到指定的配置。这里我们把 Config 作为 Redis 依赖的东西注入到了 Redis 类的实例里面去用。
绑定
把类绑定到 IoC 容器里,可以这样做:
const Redis = require('./Redis') const { ioc } = require('@adonisjs/fold') ioc.bind('My/Redis', (app) => { const Config = app.use('Adonis/Src/Config') return new Reids(Config) })
先导入自己定义的 Redis 类,然后从 @adonisjs/fold 这个模块里导入 ioc,再使用 ioc 的 bind 方法去做绑定。 bind 方法有两个参数,第一个参数是要绑定的名字。第二个参数是个 factory 函数,每次向 IoC 容器要绑定的东西的时候都会执行这个函数。
在函数里,用了一个 app.use 得到容器里的 Config,在创建 Redis 类的时候,我们把 Config 交给了 Redis。最后再返回这个配置好的 Reids 实例。
用法
要使用 Redis 的时候,可以这样:
config redis = ioc.use('My/Redis')
或者使用全局的 use 方法:
config redis = use('My/Redis')
Singletons
上面的方法有个问题,就是每次从 IoC 容器那里提取 Redis 的时候,它都会给我们一个新的实例,就是去创建一个新的 Redis 服务器连接。解决这个问题,可以定义 singletons:
ioc.singleton('My/Redis', (app) => { const Config = app.use('Adonis/Src/Config') return new Reids(Config) })
这次绑定的时候不用 bind 了,而是用的 singleton 方法。
解决依赖
在 IoC 容器那里得到想要的东西,可以使用 use 方法,把名字交给这个方法就行了。
const redis = ioc.use('My/Redis')
也可以使用全局 use 方法。
const redis = use('My/Redis')
得到 IoC 容器里面装的东西,流程是这样的:
- 先检查注册的 fake。
- 然后找到需要的绑定。
- 检查有没有使用别名,如果有,就用真实的绑定名重复上面的整个过程。
- 自动加载路径。
- 回退到使用 Node.js 原生的 require 方法导入模块。
别名
Ioc 容器里绑定的东西必须得是唯一的,所以绑定的名字有个模式:ProjectName/Scope/Module,比如:Adonis/Src/Config。Adonis 是项目的名字,Src 属于作用域,Config 是模块的真正的名字。
我们可以为绑定的名字设置别名,这样更容易记住跟输入。Adonis 项目里,已经为一些内置的模块设置了别名,比如 Route,View,Model 等等。我们也可以去覆盖这些别名,像这样:
aliases: { MyRoute: 'Adonis/Src/Route' }
下次用的时候,要这样了:
const Route = use('MyRoute')
自动加载
定义一个目录,让它可以被 IoC 容器自动加载。这并不会加载目录里面的所有的文件,只是把目录的路径作为解决依赖过程的一部分。
比如,在 AdonisJs 里的 app 目录是自动加载的,它是在 App 这个命名空间之下。就是你可以导入 app 目录下面的所有文件,不用输入文件的相对路径。
比如有个文件(app/Services/Foo.js):
class FooService { } module.exports = FooService
导入上面的文件,可以这样做:
const Foo = use('App/Services/Foo')
如果正常导入这个文件的话,应该是这样的:
require('../../Services/Foo')Node.js
评论
感觉视频讲的还是不够详细。。可以继续录视频讲讲adonis的,比较号称node版的laravel
6 年 10 个月 以前
别急,还有一大堆,不过可能得过一阵子发布。
6 年 10 个月 以前
跟laravel比起来哪个更优雅(?)呢?
6 年 10 个月 以前
如果你更喜欢 JavaScript 语言,那用这个 Adonis.js 框架就行。
6 年 10 个月 以前
Adonis开发完成之后怎么发布上线?
6 年 10 个月 以前
就像普通的 Node.js 应用一样。参考:https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js...
6 年 10 个月 以前
为什么要用Adonis.js呢?我相信学之前的同学,都想知道这个问题。
6 年 10 个月 以前
其实用啥框架都在 Node.js 这个生态里边儿。选择这个 MVC 框架主要是因为,它为应用提供了一套结构,也可以说提供了一套标准的开发方法。
6 年 10 个月 以前