Node.js模块连接模式:硬编码依赖与依赖注入
立即解锁
发布时间: 2025-08-19 01:17:25 阅读量: 1 订阅数: 4 


Node.js设计模式与最佳实践
### Node.js 模块连接模式:硬编码依赖与依赖注入
#### 1. 单例模式在 Node.js 中的情况
在 Node.js 里,传统意义上的单例模式并不存在。不同的包加载同一个模块时,可能会得到不同的实例。例如,`packageA` 和 `packageB` 加载 `mydb` 模块,由于解析路径不同,会得到不同的实例。若要实现真正的单例,可使用全局变量存储实例,如 `global.db = new Database('my-app-db')`,但这种做法应尽量避免。在本文中,为简便起见,将模块导出的有状态对象称为单例,它与传统单例模式有相同的实用意图,即便于在不同组件间共享状态。
#### 2. 模块连接模式概述
在讨论了依赖和耦合的基本理论后,接下来介绍主要的模块连接模式,重点关注有状态实例的连接,因为它们是应用中最重要的依赖类型。
#### 3. 硬编码依赖
硬编码依赖是两个模块间最传统的关系。在 Node.js 中,客户端模块使用 `require()` 显式加载另一个模块时,就形成了硬编码依赖。这种方式简单有效,但对于有状态实例的硬编码依赖,会限制模块的可重用性。
##### 3.1 使用硬编码依赖构建认证服务器
下面通过构建一个简单的认证服务器来演示硬编码依赖的使用。该服务器提供两个 HTTP API:
- `POST '/login'`:接收包含用户名和密码的 JSON 对象进行认证,成功后返回 JSON Web Token (JWT)。
- `GET '/checkToken'`:从 GET 查询参数中读取令牌并验证其有效性。
为实现这个服务器,将使用 `express` 实现 Web API,使用 `levelup` 存储用户数据。
**3.1.1 db 模块**
创建 `lib/db.js` 文件,代码如下:
```javascript
const level = require('level');
const sublevel = require('level-sublevel');
module.exports = sublevel(
level('example-db', {valueEncoding: 'json'})
);
```
此模块创建一个连接到 `./example-db` 目录下的 LevelDB 数据库的实例,并使用 `sublevel` 插件进行装饰,导出的对象是数据库句柄,是一个有状态的单例。
**3.1.2 authService 模块**
实现 `lib/authService.js` 模块,代码如下:
```javascript
// ...
const db = require('./db');
const users = db.sublevel('users');
const tokenSecret = 'SHHH!';
exports.login = (username, password, callback) => {
users.get(username, function(err, user) {
// ...
});
};
exports.checkToken = (token, callback) => {
// ...
users.get(userData.username, function(err, user) {
// ...
});
};
```
该模块实现了 `login()` 和 `checkToken()` 服务,用于验证用户凭证和令牌有效性。这里硬编码了对 `db` 模块的依赖,这意味着不修改代码就无法将 `authService` 与其他数据库实例一起使用。
**3.1.3 authController 模块**
实现 `lib/authController.js` 模块,代码如下:
```javascript
const authService = require('./authService');
exports.login = (req, res, next) => {
authService.login(req.body.username, req.body.password,
(err, result) => {
// ...
}
);
};
exports.checkToken = (req, res, next) => {
authService.checkToken(req.query.token,
(err, result) => {
// ...
}
);
};
```
该模块实现了两个 Express 路由,依赖于 `authService` 模块,而 `authService` 又依赖于 `db` 模块,这种硬编码依赖会在整个应用结构中传播。
**3.1.4 app 模块**
实现应用的入口点 `app.js`,代码如下:
```javascript
const express = require('express');
const bodyParser = require('body-parser');
const errorHandler = require('errorhandler');
const http = require('http');
const authController = require('./lib/authController');
const app = module.exports = express();
app.use(bodyParser.json());
app.post('/login', authController.login);
app.get('/checkToken', authController.checkToken);
app.use(errorHandler());
http.createServer(app).listen(3000, () => {
console.log('Express server started');
});
```
此模块将所有组件组合在一起,硬编码了对 `authController` 有状态实例的依赖。
##### 3.2 运行认证服务器
在运行认证服务器前,需使用 `populate_db.js` 脚本向数据库填充示例数据。然后运行以下命令启动服务器:
```bash
node app
```
可使用 REST 客户端或 `curl` 命令测试两个 Web 服务。例如,登录命令如下:
```bash
curl -X POST -d '{"username": "alice", "password":"secret"}' http://localhost:3000/login -H "Content-Type: application/json"
```
使用返回的令牌测试 `/checkToken` 服务:
```bash
curl -X GET -H "Accept: application/json" http://localhost:3000/checkToken?token=<TOKEN HERE>
```
##### 3.3 硬编码依赖的优缺点
- **优点**:直观的组织方式,易于理解和调试,每个模块可自行初始化和连接,无需外部干预。
- **缺点**:有状态实例的硬编码依赖会限制模块的可重用性和单元测试的难度。例如,`authService` 难以与其他数据库实例一起使用,且难以独立测试。不过,若使用 `require()` 加载无状态模块,如工厂函数、构造函数或无状态函数集,则不会出现这些问题。
#### 4. 依赖注入
依赖注入(DI)模式常被误解,很多人将其与框架和 DI 容器(如 Java 和 C# 的 Spring、PHP 的 Pimple)联系起来,但实际上它是一个更简单的概念。其核心思想是组件的依赖由外部实体提供,这个外部实体可以是客户端组件或全局容器。使用 DI 可提高模块的解耦性,尤其是对于依赖有状态实例的模块。
#### 5. 使用依赖注入重构认证服务器
重构模块以使用 DI 的方法是创建工厂函数,将依赖作为参数传入。
##### 5.1 重构 db 模块
将 `lib/db.js` 模块重构为工厂函数,代码如下:
```javascript
const level = require('level');
const sublevel = require('level-sublevel');
module.exports = dbName => {
return sublevel(
level(dbName, {val
```
0
0
复制全文
相关推荐










