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元

技术优点

  • 解耦调用方与具体类:业务层不需要知道WechatPaymentAlipayPayment的存在
  • 集中管理创建逻辑:新增支付类型只需修改工厂类
  • 天然支持依赖注入:可通过配置文件动态加载处理器

典型陷阱

  • 未处理异常类型会导致工厂方法返回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 开发者可以架构出:

  1. 高可扩展系统:新增支付类型只需注册新工厂
  2. 资源高效利用:数据库连接池避免重复创建
  3. 动态服务治理:根据用户地域自动切换翻译服务

在未来微服务架构中,这些模式可以与Docker 容器化Kubernetes 服务发现等技术深度结合,形成更强大的动态治理能力。