1. 为什么要学习设计模式?
在 Node.js 开发中,随着项目复杂度的增加,代码的可维护性和架构的健壮性会成为核心挑战。假设你正在开发一个电商系统,订单模块需要对接微信支付、支付宝支付等多种实现方案,如果每次新增支付方式都要修改核心业务代码,这显然会引发代码污染
和测试成本飙升
的问题。设计模式就像编程领域的乐高说明书
,它能教会你如何用标准化的组件组装出既灵活又稳定的系统结构。
2. 工厂模式:产品流水线的编程实践
技术栈:Node.js + JavaScript(CommonJS 规范)
应用场景
当需要根据动态参数创建不同类型的对象时,工厂模式可以消除代码中的new
关键字直接调用构造函数带来的耦合。例如支付网关选择器:
// 支付处理器基类
class PaymentProcessor {
pay(amount) {
throw new Error('必须实现 pay 方法');
}
}
// 微信支付具体实现
class WechatPayment extends PaymentProcessor {
pay(amount) {
console.log(`微信支付成功:${amount}元`);
}
}
// 支付宝支付具体实现
class AlipayPayment extends PaymentProcessor {
pay(amount) {
console.log(`支付宝支付成功:${amount}元`);
}
}
// 支付工厂(核心隔离层)
class PaymentFactory {
static createProcessor(type) {
const processors = {
'wechat': WechatPayment,
'alipay': AlipayPayment
};
const ProcessorClass = processors[type];
if (!ProcessorClass) throw new Error('不支持的支付类型');
return new ProcessorClass();
}
}
// 业务端调用示例
const wechatPay = PaymentFactory.createProcessor('wechat');
wechatPay.pay(100); // 输出:微信支付成功:100元
技术优点
- 解耦调用方与具体类:业务层不需要知道
WechatPayment
和AlipayPayment
的存在 - 集中管理创建逻辑:新增支付类型只需修改工厂类
- 天然支持依赖注入:可通过配置文件动态加载处理器
典型陷阱
- 未处理异常类型会导致工厂方法返回
undefined
- 过度使用会导致类数量膨胀(需要配合接口约束)
3. 单例模式:全局唯一实例的精细控制
技术栈:Node.js + TypeScript(ES6 语法)
应用场景
在需要跨模块共享状态或控制资源独占访问时,单例模式能确保系统中某个类只有一个实例。典型用例包括数据库连接池、应用配置管理器:
// 数据库连接单例(TypeScript 实现)
class DatabaseConnection {
private static instance: DatabaseConnection;
private connectionPool: any;
// 构造函数私有化:阻止外部 new 操作
private constructor() {
this.connectionPool = this.initializePool();
}
// 初始化连接池的私有方法
private initializePool() {
console.log('创建数据库连接池...');
return { maxConnections: 10 };
}
// 全局访问点
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
// 业务方法示例
public query(sql: string) {
console.log(`执行 SQL:${sql}`);
}
}
// 模块 A 使用单例
const dbA = DatabaseConnection.getInstance();
dbA.query('SELECT * FROM users');
// 模块 B 获取的是同一个实例
const dbB = DatabaseConnection.getInstance();
console.log(dbA === dbB); // 输出:true
进阶实现技巧
- 使用
闭包
保护实例变量(避免原型污染) - 通过
Object.freeze
防止实例被修改 - 在 NestJS 等框架中结合
@Injectable()
装饰器实现
缺点警示
- 测试困难:单例状态会在测试用例间共享
- 违背单一职责原则:需谨慎评估是否真的需要全局唯一性
4. 服务定位器:动态服务治理的利器
技术栈:Node.js + JavaScript(ES Modules)
应用场景
当系统需要动态切换服务实现或按需延迟加载模块时,服务定位器可以作为中央服务注册表
。例如多语言翻译服务的选择:
// 服务定位器容器
class ServiceLocator {
static services = new Map();
// 注册服务
static register(name, implementation) {
this.services.set(name, implementation);
}
// 获取服务(支持依赖注入)
static get(name) {
const service = this.services.get(name);
if (!service) throw new Error(`未注册的服务:${name}`);
return typeof service === 'function' ? service() : service;
}
}
// 中文翻译服务
const zhTranslator = {
translate(text) {
return `${text} 的简体中文翻译`;
}
};
// 英文翻译服务
const enTranslator = {
translate(text) {
return `Translation of ${text}`;
}
};
// 注册服务(可在启动时动态配置)
ServiceLocator.register('translator', zhTranslator);
// 业务模块使用
const translator = ServiceLocator.get('translator');
console.log(translator.translate('欢迎')); // 输出:欢迎 的简体中文翻译
// 动态切换服务
ServiceLocator.register('translator', enTranslator);
console.log(translator.translate('Welcome')); // 输出:Translation of Welcome
与依赖注入的差异
- 主动获取 vs 被动接收:服务定位器需要主动调用
get()
方法 - 更适合动态环境:微服务架构中常见的使用场景
性能注意事项
- 频繁调用
get()
会有查找开销 - 建议配合缓存机制使用
5. 模式对比与选型指南
模式类型 | 适用阶段 | 典型场景 | 注意事项 |
---|---|---|---|
工厂模式 | 对象创建期 | 多态对象创建 | 防止工厂类成为上帝对象 |
单例模式 | 系统初始化期 | 全局资源配置 | 谨慎处理多线程环境(如果有) |
服务定位器 | 运行时动态调整 | 插件化系统、多版本服务并存 | 需设计完善的异常处理机制 |
6. 模式融合实战案例
在电商订单系统中结合三种模式:
// 使用单例管理支付网关配置
class PaymentConfig {
static instance;
constructor() {
this.gateways = { wechat: {}, alipay: {} };
}
static getInstance() {
if (!this.instance) this.instance = new PaymentConfig();
return this.instance;
}
}
// 工厂模式生成支付处理器
class PaymentProcessorFactory {
static create(gatewayType) {
const config = PaymentConfig.getInstance().gateways[gatewayType];
return new PaymentProcessor(config);
}
}
// 服务定位器管理第三方服务
ServiceLocator.register('riskControl', new RiskControlService());
// 业务逻辑
function processOrder(order) {
const processor = PaymentProcessorFactory.create(order.gateway);
const riskService = ServiceLocator.get('riskControl');
if (riskService.check(order)) {
processor.charge(order.amount);
}
}
总结与展望
通过合理运用工厂模式、单例模式和服务定位器,Node.js 开发者可以架构出:
- 高可扩展系统:新增支付类型只需注册新工厂
- 资源高效利用:数据库连接池避免重复创建
- 动态服务治理:根据用户地域自动切换翻译服务
在未来微服务架构中,这些模式可以与Docker 容器化
、Kubernetes 服务发现
等技术深度结合,形成更强大的动态治理能力。