4

Node.js 设计模式笔记 —— 工厂模式

 2 years ago
source link: https://rollingstarky.github.io/2022/04/28/node-js-design-patterns-factory-pattern/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Node.js 设计模式笔记 —— 工厂模式

发表于 2022-04-28

| 分类于 Program

| 0

| 阅读次数:

字数统计: 4k

|

阅读时长 ≈ 0:04

工厂(Factory)模式 是 Node.js 中最常见的设计模式之一。
其具有以下优势:

  • 将对象的创建过程与对象的实现细节进行解耦。由工厂创建一系列对象,某个对象继承的特征在运行时确定
  • 工厂模式允许我们对外暴露更少的接口。一个类可以被扩展或者操控,而工厂本身仅仅是一个负责创建对象的函数,没有给用户其他选项,从而使接口更健壮和容易理解
  • 借助闭包可以帮助强化对象的封装

解耦对象的创建和实现

工厂模式封装了新对象的创建过程,给这个过程提供了更多的灵活性和控制。在工厂内部我们可以选择各种不同的方式来创建某个对象的实例,工厂的消费者对于这些细节一无所知。
相反地,使用 new 关键字则会将代码绑定到一种特定的创建方式上。

比如下面的一个用于创建 Image 对象的工厂函数:

function createImage (name) {
return new Image(name)
}
const image = createImage('photo.jpeg')

上述 createImage 工厂函数看上去完全没有必要,直接使用如下一行代码就可以搞定:

const image = new Image('photo.jpeg')

按照前面所说,new 关键字会将代码绑定给一种特定类型的对象,在这里就是 Image 类型。
而工厂模式则更加灵活。假设需要重构 Image 类,将其分割成几个更小的类型,对应不同的图片格式。
工厂函数 createImage 作为唯一的创建新图片对象的方式,即便需要创建的图片对象添加了更多的类型,也可以很简单地只对 createImage 的内部逻辑进行重写,其对外开放的接口不会发生改变,不会破坏任何现有的代码:

function createImage(name) {
if (name.match(/\.jpe?g$/)) {
return new ImageJpeg(name)
} else if (name.match(/\.gif$/)) {
return new ImageGif(name)
} else if (name.match(/\.png$/)) {
return new ImagePng(name)
} else {
throw new Error('Unsupported format')
}
}

借助闭包,工厂模式可以成为一种强化封装性的机制。

function createPerson (name) {
const privateProperties = {}

const person = {
setName (name) {
if (!name) {
throw new Error('A person must have a name')
}
privateProperties.name = name
},
getName () {
return privateProperties.name
}
}

person.setName(name)
return person
}

person = createPerson('John')
console.log(person.getName())
// => John
person.setName('Michael')
console.log(person.getName())
// => Michael

createPerson 工厂函数创建了一个 person 对象。由于闭包的存在,即便 createPerson 函数运行完毕退出了,其属性 privateProperties 仍可以被 person 对象通过其 setNamegetName 方法访问。
但与此同时,该 privateProperties 属性无法被任何外部对象(包括 person)直接访问。

完整实例:Profiler

创建并进入一个新的 simple_profiler 文件夹,编辑如下内容的 package.json 文件:

{
"type": "module"
}

创建如下内容的 profiler.js 文件:

class Profiler {
constructor (label) {
this.label = label
this.lastTime = null
}

start () {
this.lastTime = process.hrtime()
}

end () {
const diff = process.hrtime(this.lastTime)
console.log(`Timer "${this.label}" took ${diff[0]} seconds ` + `and ${diff[1]} nanoseconds.`)
}
}

const noopProfiler = {
start () {},
end () {}
}

export function createProfiler (label) {
if (process.env.NODE_ENV === 'production') {
return noopProfiler
}

return new Profiler(label)
}

创建如下内容的 index.js 文件:

import { createProfiler } from './profiler.js'

function getAllFactors (intNumber) {
const profiler = createProfiler(
`Finding all factors of ${intNumber}`
)

profiler.start()
const factors = []
for (let factor = 2; factor <= intNumber; factor++) {
while ((intNumber % factor) === 0) {
factors.push(factor)
intNumber = intNumber / factor
}
}
profiler.end()

return factors
}

const myNumber = process.argv[2]
const myFactors = getAllFactors(myNumber)
console.log(`Factors of ${myNumber} are: `, myFactors)

运行效果:

$ NODE_ENV=production node index.js 2201307499
Factors of 2201307499 are: [ 38737, 56827 ]
$ node index.js 2201307499
Timer "Finding all factors of 2201307499" took 0 seconds and 9738800 nanoseconds.
Factors of 2201307499 are: [ 38737, 56827 ]

简单来说,就是通过 createProfiler 工厂函数来创建不同的 Profiler 对象。若环境变量 NODE_ENV 的值为 production,则返回一个新的的 noopProfiler,不对运行的代码做任何额外的操作;若 NODE_ENV 的值不为 production,则返回一个新的 Profiler 对象,记录程序运行的时间。

Node.js Design Patterns: Design and implement production-grade Node.js applications using proven patterns and techniques, 3rd Edition


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK