本文深入解析 JavaScript 中的工厂模式,这是一种遵循 DRY 原则的创建型设计模式。通过日志工具 Logger 的实际案例,演示如何从基础的 if-else 实现重构为基于对象映射的工厂函数。文章详细阐述了动态创建对象、封装构造细节以及提升代码可维护性的优势,并展示了如何利用开闭原则简化新增类型的过程。
本文通过具体实例,详细解析 JavaScript 中工厂模式的定义及其实现方式,旨在为读者提供清晰的参考与学习素材。
前言
在前一篇文章中,我们探讨了单例模式,尚未阅读该内容的读者可参考以下链接进行回顾:
浅析 JS 设计模式之:单例模式
本文将重点介绍一种在开发中极为常见的设计模式:工厂模式。
工厂模式属于创建型设计模式,其核心在于遵循 DRY(Don't Repeat Yourself)原则。该模式允许代码依据特定的输入或既定规则,自动判断并创建相应类型的对象。简而言之,它的主要功能是动态返回用户所需的实例对象。
回顾上次的例子
为了保持连贯性,我们继续沿用单例模式中的案例,即一个日志工具 Logger:
class Logger {
log (...args) {
console.log(...args);
}
}
上述代码展示了核心 API。每次使用时,都需要通过 new Logger() 来实例化一个 logger 对象,随后即可像使用 console 那样调用其方法。
多种 Logger
假设当前项目需要支持 Electron 环境,这意味着日志输出既可以是控制台日志,也可以是文件日志。因此,我们需要定义两种不同类型的 Logger:
ConsoleLogger
// logger/console.js
class ConsoleLogger {
log (...args) {
console.log(...args)
}
}
export default ConsoleLogger
FileLogger
// logger/file.js
class FileLogger {
log (...args) {
dumpLog(...args)
}
}
export default FileLogger
此处暂不深究 dumpLog 的具体实现逻辑,只需明确其功能是将日志数据写入文件即可。
使用工厂
目前我们已拥有两种类型的 Logger,虽然可以直接在项目中导入各自类型的模块并使用,但这会导致每次使用都需单独导入对应模块,操作如下所示:
使用 console logger
import ConsoleLogger from './logger/console'
const logger = new ConsoleLogger()
使用 file logger
import FileLogger from './logger/file'
const logger = new FileLogger()
这种调用方式显得较为繁琐。若未来还需支持远程日志等其他类型的 Logger,使用方式将更加繁杂。为了统一 Logger 模块的使用接口,此时引入工厂模式便显得尤为必要。
接下来,我们新建一个 index.js 文件:
// logger/index.js
import ConsoleLogger from './console.js'
import FileLogger from './file.js'
function createLogger(type = 'console') {
if (type === 'console') {
return new ConsoleLogger()
} else if (type === 'file') {
return new FileLogger()
}
throw new Error(`Logger type not found: ${type}`)
}
export default createLogger
经过上述封装后,我们的使用方式将简化为如下形式:
import createLogger from './logger'
// console logger
const logger1 = createLogger('console')
// file logger
const logger2 = createLogger('file')
重构一下
前文示例中的 if-else 结构并不够优雅,且随着 Logger 类型增加,维护成本将显著上升。我们可以利用对象映射来替代条件判断,从而消除 if-else 结构,同时引入一个 logger 选项配置。
// logger/index.js
import ConsoleLogger from './console.js'
import FileLogger from './file.js'
const loggerMap = {
console: ConsoleLogger,
file: FileLogger
}
// 可选参数一般放在最后面
function createLogger(options, type = 'console') {
const Logger = loggerMap[type]
if (Logger) {
return new Logger(options)
}
throw new Error(`Logger type not found: ${type}`)
}
上述封装方式符合SOLID原则中的开闭原则,即对扩展开放,对修改关闭。每当需要新增一种logger类型时,只需新增一个文件,并将对应的构造器注册到loggerMap中即可。外部调用方式保持不变,从而以最小的修改代价实现了功能扩展,这体现了良好的设计效果。
总结
以下是对工厂模式主要优点的回顾:
- 动态创建对象:适用于需要在运行时确定对象类型的场景。
- 抽象:封装了对象创建的具体细节,用户无需接触对象的构造器,只需指定所需对象类型。
- 可用性与可维护性:通过工厂统一管理相似对象,提供统一的创建接口,符合开闭原则。这使得我们能够轻松添加多种类型的对象,而无需修改大量现有代码。
至此,工厂模式的介绍暂告一段落。下次我们将探讨装饰器模式。
参考内容
- JavaScript Object Oriented Patterns: Factory Pattern
- 《JavaScript 设计模式》
- 《JavaScript 面向对象编程指南》
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。