设计模式的目的: 解决反复出现的问题
而提出的可重用解决方案
创建型设计模式: 解决对象的创建问题
Factory 工厂模式
工厂模式: 将对象创建的逻辑
封装成函数
工厂函数只关心是否可以制造出实例
就行, 而不关心其实现细节
创建一个测评类, 非生产模式下, 测试函数运行时间
const changeColor = color => input => `\x1b[${color}m${input}\x1b[0m`
const changeGreen = changeColor(92)
const changeViolet = changeColor(95)
class Profiler {
constructor(label) {
this.label = label ? label : 'timer'
this.lastTime = null
}
start() {
this.lastTime = process.hrtime()
}
end() {
const diff = process.hrtime(this.lastTime)
console.log(`${changeViolet(this.label)}: ${changeGreen(diff[0])} s ${changeGreen(diff[1])} ns`)
}
}
const noopProfiler = {
start() { },
end() { }
}
const createProfiler = label => {
if (process.env.NODE_ENV === 'production') {
return noopProfiler
}
return new Profiler(label)
}
export const useProfiler = (fn, label) => {
const profiler = createProfiler(label)
profiler.start()
const res = fn()
profiler.end()
return res
}
使用如下
import { useProfiler } from './profiler.js'
// 对给定数字进行因式分解
function getAllFactors(intNumber) {
const factors = []
for (let factor = 2; factor <= intNumber; factor++) {
while ((intNumber % factor) === 0) {
factors.push(factor)
intNumber = intNumber / factor
}
}
return factors
}
const myNumber = process.argv[2]
const myFactors = useProfiler(() => getAllFactors(myNumber), 'getAllFactors')
console.log(`Factors of ${myNumber} are: `, myFactors)
Builder 建造者模式
使用者可一步一步创建复杂的对象
实现一个 url 构造器, 使用如下
import { UrlBuilder } from './urlBuilder.js'
const url = new UrlBuilder()
.setProtocol('https')
.setAuthentication('user', 'pass')
.setHostname('example.com')
.build()
console.log(url.toString()) // https://user:pass@example.com
import { Url } from './url.js'
export class UrlBuilder {
setProtocol (protocol) {
this.protocol = protocol
return this
}
setAuthentication (username, password) {
this.username = username
this.password = password
return this
}
setHostname (hostname) {
this.hostname = hostname
return this
}
setPort (port) {
this.port = port
return this
}
setPathname (pathname) {
this.pathname = pathname
return this
}
setSearch (search) {
this.search = search
return this
}
setHash (hash) {
this.hash = hash
return this
}
build () {
return new Url(this.protocol, this.username, this.password,
this.hostname, this.port, this.pathname, this.search,
this.hash)
}
}
export class Url {
constructor (protocol, username, password, hostname,
port, pathname, search, hash) {
this.protocol = protocol
this.username = username
this.password = password
this.hostname = hostname
this.port = port
this.pathname = pathname
this.search = search
this.hash = hash
this.validate()
}
validate () {
if (!this.protocol || !this.hostname) {
throw new Error('Must specify at least a ' +
'protocol and a hostname')
}
}
toString () {
let url = ''
url += `${this.protocol}://`
if (this.username && this.password) {
url += `${this.username}:${this.password}@`
}
url += this.hostname
if (this.port) {
url += this.port
}
if (this.pathname) {
url += this.pathname
}
if (this.search) {
url += `?${this.search}`
}
if (this.hash) {
url += `#${this.hash}`
}
return url
}
}
Revealing Constructor 模式
只在构建中的时候, 使用私密属性
比如
new Promise((resolve, reject) => {
})
promise 有个状态, 其状态在 Promise 对象创建好之后, 便不可修改 (不考虑使用 setTimeout 等异步情况)
Singleton 单例模式
确保某个类只能出现一个实例
从模块中导出一个实例, 基本就做到了单例模式所应具备的效果
import { Database } from './Database.js'
export const dbInstance = new Database('my-app-db', {
url: 'localhost:5432',
username: 'user',
password: 'password'
})
但是, 我们无法保证包与包之间的实例也相同
node_modules
-- package-a
---- node_modules
------ mydb version: 1.0
-- package-b
---- node_modules
------ mydb version: 2.0
管理模块之间的依赖
单例
导出一个实例: export default new DataBase()
博客模块: 从数据库中获取或插入一篇文章, 依赖于数据库模块
用户可通过博客模块提供的 API 操作数据库
import { Blog } from './blog.js'
async function main() {
const blog = new Blog()
await blog.insertPost(
'dependency-injection',
'Dependency injection in Node.js',
'Today we will discuss about dependency injection in Node.js\n\n...',
new Date('2022-04-08')
)
const posts = await blog.getAllPosts()
if (posts.length === 0) {
console.log('No post available. Run `node import-posts.js`' +
' to load some sample posts')
}
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)
blog.js
import db from './db.js'
export class Blog {
insertPost(id, title, content, created_at) {
db.insertPost(id, title, content, created_at)
}
getAllPosts() {
return db.getAllPosts()
}
}
db.js
模拟的数据库
const posts = [
{
id: 'my-first-post',
title: 'My first post',
content: 'Hello World!\nThis is my first post',
created_at: new Date('2022-04-06')
}
]
class MockDataBase {
constructor() {
this.posts = posts
}
insertPost(id, title, content, created_at) {
this.posts.push({ id, title, content, created_at })
}
getAllPosts() {
return this.posts
}
}
export default new MockDataBase()
DI 依赖注入
由外部传入依赖的东西
在 blog.js
中, blog.js
模块 与 db.js
模块之间紧密耦合, 如果没有 db.js
模块, 那么, blog.js
模块将无法正常工作
重构 blog.js
export class Blog {
constructor(db) {
this.db = db
}
insertPost(id, title, content, created_at) {
this.db.insertPost(id, title, content, created_at)
}
getAllPosts() {
return this.db.getAllPosts()
}
}
具体使用 blog.js
如下
import { Blog } from './blog.js'
import db from './db.js'
async function main() {
const blog = new Blog(db)
// .......
}
main().catch(console.error)
来自: <Nodejs 设计模式第三版> 第七章-创建型的设计模式