单例(Singleton)模式是面向对象编程中最常见的设计模式之一,Node.js 已经有了很简单的实现。
使用单例模式的目的在于确保某个类只有一个实例存在,并对该实例的访问进行统一的控制。其主要运用场景如下:
比如,一个标准的 Database 类会提供对数据库的访问:
// 'Database.js'
export class Database {
constructor(dbName, connectionDetails) {
// ...
}
// ...
}
在上述类的标准实现中,通常需要维护一个数据库连接池,毕竟为每一次数据库请求都分别创建一个新的 Database 实例显得不太现实。此外,Database 实例可能会保存部分有状态的数据,比如 pending 的事务列表。
因此,一般只在应用开始运行时初始化一个 Database 实例,此后其作为一个唯一的共享实例被所有其他组件使用。
Node.js 的新用户可能会思考该如何从逻辑层面实现单例模式,事实上远比想象中更简单。
将某个实例从模块中导入,即可实现单例模式的所有需求。
// file 'dbInstance.js'
import {Database} from './Database.js'
export const dbInstance = new Database('my-app-db', {
url: 'localhost:5432',
username: 'user',
password: 'password'
})
只需要简单地导出 Database 类的一个新实例(dbInstance),在当前的整个包中就可以认为只存在这一个 dbInstance 对象(单例),这得益于 Node.js 的模块系统。Node.js 会对模块进行缓存,保证不会在每次导入时都再执行一遍代码。
再通过如下一行代码即可简单地获取上面创建的共享的 dbInstance 实例:
import { dbInstance } from './dbInstance.js'
Node.js 中缓存的模块以完整路径作为对其进行查找的 key,所以前面实现的 Singleton 只在当前的包中生效。每个包都有可能包含其私有的依赖,放置在它自己的 node_modules 路径下。因而就可能导致同一个模块存在多个实例,前面实现的 Singleton 不能再保证唯一性。
例如,前面的 Database.js 和 dbInstance.js 同属于 mydb 包,其 package.json 内容如下:
{
"name": "mydb",
"version": "2.0.0",
"type": "module",
"main": "dbInstance.js"
}
又假设有两个包(package-a 和 package-b)各自都拥有包含如下内容的 index.js 文件:
import {dbInstance} from 'mydb'
export function getDbInstance() {
return dbInstance
}
package-a 和 package-b 都依赖包 mydb,但 package-a 依赖版本 1.0.0,package-b 依赖版本 2.0.0。结果就会出现如下结构的依赖关系:
app/
`-- node_modules
|-- package-a
| `-- node_modules
| `-- mydb
`-- package-b
`-- node_modules
`-- mydb
当 package-a 和 package-b 依赖两个不兼容版本的 mydb 模块时,包管理器不会将 mydb 放置在 node_modules 的根路径下,而是在 package-a 和 package-b 下面各自放一个私有的 mydb 副本,从而解决版本冲突。
此时假如 app/ 路径下有一个如下内容的 index.js:
import {getDbInstance as getDbFromA} from 'package-a'
import {getDbInstance as getDbFromB} from 'package-b'
const isSame = getDbFromA() === getDbFromB()
console.log('Is the db instance in package-a the same ' +
`as package-b? ${isSame ? 'YES' : 'NO'}`)
getDbFromA() 和 getDbFromB() 并不会获得同一个 dbInstance 实例,打破了 Singleton 模式的假设。
当然了,大多数情况下我们并不需要一个 pure Singleton。事实上,通常也只会在应用的 main 包中创建和导入 Singleton。
最简单地将两个模块组合在一起的方式,就是直接利用 Node.js 的模块系统。如前面所说,这样组合起来的有状态的依赖关系其实就是单例模式。
实现下面一个博客系统:
mkdir blog && cd blog
npm install sqlite3
blog/package.json:
{
"type": "module",
"dependencies": {
"sqlite3": "^5.0.8"
}
}
blog/db.js:
import {dirname, join} from 'path'
import {fileURLToPath} from 'url'
import sqlite3 from 'sqlite3'
const __dirname = dirname(fileURLToPath(import.meta.url))
export const db = new sqlite3.Database(
join(__dirname, 'data.sqlite')
)
blog/blog.js:
import {promisify} from 'util'
import {db} from './db.js'
const dbRun = promisify(db.run.bind(db))
const dbAll = promisify(db.all.bind(db))
export class Blog {
initialize() {
const initQuery = `CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
return dbRun(initQuery)
}
createPost(id, title, content, createdAt) {
return dbRun('INSERT INTO posts VALUES (?, ?, ?, ?)',
id, title, content, createdAt)
}
getAllPosts() {
return dbAll('SELECT * FROM posts ORDER BY created_at DESC')
}
}
blog/index.js:
import {Blog} from './blog.js'
async function main() {
const blog = new Blog()
await blog.initialize()
const posts = await blog.getAllPosts()
if (posts.length === 0) {
console.log('No posts available.')
}
for (const post of posts) {
console.log(post.title)
console.log('-'.repeat(post.title.length))
console.log(`Published on ${new Date(post.created_at).toISOString()}`)
console.log(post.content)
}
}
main().catch(console.error)
db.js 创建了一个 db 数据库实例并导出,blog.js 从 db.js 中导入 db 实例并直接在代码中使用。形成了一种简单直观的 blog.js 依赖于 db.js 模块的关系。同时整个项目中的数据库连接都由唯一的 db 单例进行控制。
运行效果:
$ node index.js
No posts available.
可以运行下面的命令插入测试数据:
// import-posts.js
import {Blog} from './blog.js'
const posts = [
{
id: 'my-first-post',
title: 'My first post',
content: 'Hello World!\nThis is my first post',
created_at: new Date('2020-02-03')
},
{
id: 'iterator-patterns',
title: 'Node.js iterator patterns',
content: 'Let\'s talk about some iterator patterns in Node.js\n\n...',
created_at: new Date('2020-02-06')
},
{
id: 'dependency-injection',
title: 'Dependency injection in Node.js',
content: 'Today we will discuss about dependency injection in Node.js\n\n...',
created_at: new Date('2020-02-29')
}
// ...
]
async function main() {
const blog = new Blog()
await blog.initialize()
await Promise.all(
posts.map(
(post) => blog.createPost(
post.id,
post.title,
post.content,
post.created_at
)
)
)
console.log('All posts imported')
}
main().catch(console.error)
$ node import-posts.js
All posts imported
$ node index.js
Dependency injection in Node.js
-------------------------------
Published on 2020-02-29T00:00:00.000Z
Today we will discuss about dependency injection in Node.js
...
Node.js iterator patterns
-------------------------
Published on 2020-02-06T00:00:00.000Z
Let's talk about some iterator patterns in Node.js
...
My first post
-------------
Published on 2020-02-03T00:00:00.000Z
Hello World!
This is my first post
就如上面的代码所示,借助 Singleton 模式,将 db 实例自由地在文件之间传递,可以实现一个很简单的命令行博客管理系统。这也是大多数情况下我们管理有状态的依赖的方式。
使用 Singleton 诚然是最简单、即时,可读性最好的方案。但是,假如我们需要在测试过程中 mock 数据库,或者需要终端用户能够自主选择另一个数据库后端,而不是默认提供的 SQLite。
对于以上需求,Singleton 反而成为了一个设计更好结构的阻碍。可以在 db.js 中引入 if 语句根据某些条件来选择不同的实现,显然这种方式并不是很美观。
Node.js 的模块系统以及 Singleton 模式可以作为一个很好的管理和组合应用组件的工具,它们非常简单,容易上手。但另一方面,它们也可能会使各组件之间的耦合程度加深。
在前面的例子中,blog.js 和 db.js 模块是耦合度很高的,blog.js 没有了 db.js 就无法工作,当然也无法使用另一个不同的数据库模块。
可以借助 Dependency Injection 来弱化模块之间的耦合度。
依赖注入表示将某个组件的依赖模块由外部实体(injector)作为输入提供。
DI 的主要优势在于能够降低耦合度,尤其当模块依赖于有状态的实例(比如数据库连接)时。每个依赖项并不是硬编码进主体代码,而是由外部传入,意味着这些依赖项可以被替换成任意相互兼容的实例。使得主体代码本身可以以最小的改动在不同的背景下重用。

修改 blog.js:
import {promisify} from 'util'
export class Blog {
constructor(db) {
this.db = db
this.dbRun = promisify(db.run.bind(db))
this.dbAll = promisify(db.all.bind(db))
}
initialize() {
const initQuery = `CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
return this.dbRun(initQuery)
}
createPost(id, title, content, createdAt) {
return this.dbRun('INSERT INTO posts VALUES (?, ?, ?, ?)',
id, title, content, createdAt)
}
getAllPosts() {
return this.dbAll('SELECT * FROM posts ORDER BY created_at DESC')
}
}
最主要的改动在于为 Blog 类添加了 constructor (db) 构造方法,该方法的参数 db 即为 Dependency,Blog 的依赖项,需要在运行时由 Blog 的客户端提供。
修改 db.js:
import sqlite3 from 'sqlite3'
export function createDb(dbFile) {
return new sqlite3.Database(dbFile)
}
此版本的 db 模块提供了一个 createDB() 工厂函数,可以在运行时返回一个新的数据库实例。
修改 index.js:
import {dirname, join} from 'path'
import {fileURLToPath} from 'url'
import {Blog} from './blog.js'
import {createDb} from './db.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
async function main() {
const db = createDb(join(__dirname, 'data.sqlite'))
const blog = new Blog(db)
await blog.initialize()
const posts = await blog.getAllPosts()
if (posts.length === 0) {
console.log('No posts available.')
}
for (const post of posts) {
console.log(post.title)
console.log('-'.repeat(post.title.length))
console.log(`Published on ${new Date(post.created_at).toISOString()}`)
console.log(post.content)
}
}
main().catch(console.error)
使用 createDB() 工厂函数创建数据库实例 db,然后在初始化 Blog 实例时,将 db 作为 Blog 的依赖进行注入。
从而 blog.js 与具体的数据库实现进行了分离。
依赖注入可以提供松耦合和代码重用等优势,但也存在一定的代价。比如无法在编码时解析依赖项,使得理解模块之间的逻辑关系变得更加困难,尤其当应用很大很复杂的时候。
此外,我们还必须确保数据库实例(依赖)在 Blog 实例之前创建,从而迫使我们手动构建整个应用的依赖图,以保证顺序正确。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl
我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:
我经常迷上ruby的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情