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

工厂(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

你可能感兴趣的:(Node.js 设计模式笔记 —— 工厂模式)