怎么在ES6 Class中实现JavaScript工厂模式

作者:青柠

时间:2026-11-03

浏览:446

本文系统讲解怎么在 ES6 Class 中实现 JavaScript 工厂模式,涵盖静态方法分发、Map 动态注册表、异步工厂及抽象工厂模式的完整代码与架构解析。

文章目录

在 ES6 中实现工厂模式,本质上是通过类(Class)或其静态方法(static)封装具有共性的子类实例化细节,使外部调用方仅通过传递类型标识或配置参数,就能直接获取具备统一契约的产品实例,而无需在业务代码各处直接书写 new 具体子类()。这种方式不仅解除了业务逻辑与具体实现的紧密耦合,还能集中管理依赖注入、全局配置以及多态实例的分发。

怎么在ES6 Class中实现JavaScript工厂模式
概览:ES6 Class 工厂模式的基本职责与对象生产流向

一、为什么要在 ES6 Class 中使用工厂模式?

在 ES6 引入原生 class 语法之前,JavaScript 开发者通常使用普通函数和原型链返回纯字面量对象或构造器实例。有了 Class 语法后,面向对象规范更为直观,但直接在业务逻辑中到处调用 new ConcreteClass() 会暴露出三个典型痛点:

  • 依赖强耦合:调用方必须事先了解每一个具体子类的类名、引入路径以及构造参数的细微差异。
  • 缺乏扩展弹性:当业务需要根据后端返回字段动态切换底层对象(如切换短信通道或支付渠道)时,散落在各处的 switch-case 或 if-else 会导致每次变更都要修改多处调用代码,违反开闭原则(OCP)。
  • 难以统一拦截预处理:直接使用 new 无法在对象返回前做全局校验、日志审计、单例复用或统一属性注入。
怎么在ES6 Class中实现JavaScript工厂模式
图 1:ES6 Class 工厂模式架构与解耦关系

引入工厂类后,调用方只与工厂类以及抽象基类定义的公用接口交互。工厂类充当调度中枢,具体子类各自继承自公用基类并实现标准化行为,结构关系如上图所示。

二、核心实现方式一:静态简单工厂与动态注册表

最直接也是工程中最常用的实现方式,是在工厂类中提供静态方法 static create(type, options)。为了避免在静态方法中堆砌冗长的 switch-case 语句,标准做法是使用 Map 维护一套类型映射注册表,使工厂具备动态扩展能力。

1. 定义抽象产品基类与具体子类

首先定义规范各子类公共行为的基类 BaseNotification,并利用 new.target 模拟抽象类,防止基类被直接意外实例化:

// 抽象产品基类
class BaseNotification {
  constructor(config) {
    if (new.target === BaseNotification) {
      throw new Error('BaseNotification 是抽象基类,不能直接实例化');
    }
    this.recipient = config.recipient;
    this.content = config.content;
  }

  // 统一约定必须实现的抽象方法
  send() {
    throw new Error('子类必须实现 send() 方法');
  }
}

// 具体产品子类 A:邮件通知
class EmailNotification extends BaseNotification {
  constructor(config) {
    super(config);
    this.subject = config.subject || '系统邮件';
  }

  send() {
    return `[邮件] 发送给 ${this.recipient},标题: ${this.subject},正文: ${this.content}`;
  }
}

// 具体产品子类 B:短信通知
class SMSNotification extends BaseNotification {
  constructor(config) {
    super(config);
    this.signName = config.signName || '系统通知';
  }

  send() {
    return `[短信] 【${this.signName}】发送给 ${this.recipient}:${this.content}`;
  }
}

2. 编写具备注册功能的工厂类

工厂类通过私有或静态 Map 存储具体的类构造器。外部既可以直接使用内置预设,也能在后续运行阶段动态注册新子类:

class NotificationFactory {
  // 使用静态 Map 维护类型标识与类引用的映射
  static registry = new Map();

  // 注册新类:实现对修改关闭、对扩展开放
  static register(type, classConstructor) {
    if (typeof classConstructor !== 'function') {
      throw new TypeError('注册的目标必须为类或构造函数');
    }
    this.registry.set(type.toLowerCase(), classConstructor);
  }

  // 核心创建方法
  static create(type, config) {
    const normalizedType = String(type).toLowerCase();
    const TargetClass = this.registry.get(normalizedType);

    if (!TargetClass) {
      throw new Error(`未注册的通知类型: ${type},当前可用: ${Array.from(this.registry.keys()).join(', ')}`);
    }

    // 可以在此统一进行参数补全、环境检测或埋点
    const finalConfig = { ...config, timestamp: Date.now() };
    return new TargetClass(finalConfig);
  }
}

// 预置标准产品类
NotificationFactory.register('email', EmailNotification);
NotificationFactory.register('sms', SMSNotification);
怎么在ES6 Class中实现JavaScript工厂模式
图 2:ES6 静态工厂执行与动态注册全流程

3. 实际调用与动态扩展效果

业务代码现在只需统一面向 NotificationFactory.create 进行调用。如果后续需要新增站内推送通知(Push),不需要更改原有工厂方法逻辑,只需要调用 register 即可生效:

// 业务调用
const emailNotice = NotificationFactory.create('email', {
  recipient: 'admin@example.com',
  subject: '监控告警',
  content: 'CPU 使用率超过 90%'
});
console.log(emailNotice.send());

// 运行时动态扩展:定义新类并注册
class PushNotification extends BaseNotification {
  send() {
    return `[站内推送] 向设备 ${this.recipient} 推送: ${this.content}`;
  }
}

// 无需修改 NotificationFactory 原代码,直接注册
NotificationFactory.register('push', PushNotification);

const pushNotice = NotificationFactory.create('push', {
  recipient: 'device_token_8899',
  content: '新版本已发布'
});
console.log(pushNotice.send());

三、核心实现方式二:工厂方法模式(Factory Method)

简单工厂将所有对象的构建逻辑集中在单个类中,当不同产品的实例化步骤(如鉴权准备、网络连接、参数解析)差异极大时,工厂方法模式更为合适。工厂方法模式的核心是将“创建具体产品”的动作延迟到子类工厂中。

// 抽象工厂基类
class LoggerFactory {
  constructor() {
    if (new.target === LoggerFactory) {
      throw new Error('LoggerFactory 为抽象工厂,不可直接实例化');
    }
  }

  // 抽象工厂方法:要求子类重写
  createLogger() {
    throw new Error('子类工厂必须实现 createLogger() 方法');
  }

  // 模版方法:定义公共业务流程
  writeLog(message) {
    const logger = this.createLogger();
    logger.log(message);
  }
}

// 具体工厂 A:文件日志工厂
class FileLoggerFactory extends LoggerFactory {
  constructor(filePath) {
    super();
    this.filePath = filePath;
  }

  createLogger() {
    // 可以包含专属于文件日志的复杂初始化
    return {
      log: (msg) => console.log(`[File: ${this.filePath}] ${msg}`)
    };
  }
}

// 具体工厂 B:控制台日志工厂
class ConsoleLoggerFactory extends LoggerFactory {
  createLogger() {
    return {
      log: (msg) => console.log(`[Console] ${msg}`)
    };
  }
}

// 使用方式
const fileFactory = new FileLoggerFactory('/var/log/app.log');
fileFactory.writeLog('数据库连接成功');
怎么在ES6 Class中实现JavaScript工厂模式
图 3:直接 new 强耦合 vs ES6 Class 工厂解耦对比

四、解决原生限制:静态异步工厂(Async Factory)

在 JavaScript ES6 Class 中,类的 constructor 必须是同步执行的,语法上不允许使用 async constructor()。然而在实际业务中,产品实例的构建往往依赖异步前置准备(例如异步读取本地证书、拉取远程鉴权凭证或建立 Socket 握手)。

工厂模式的静态方法天然支持 async/await,是解决这一语言限制的标准方案:

class DatabaseConnection {
  // 构造函数设为保护视角,只接收准备完备的参数
  constructor(rawConnection, credentials) {
    this.conn = rawConnection;
    this.auth = credentials;
  }

  // 静态异步工厂方法
  static async create(config) {
    // 1. 异步获取凭证或解析 DNS
    const credentials = await fetchRemoteAuth(config.authUrl);
    
    // 2. 异步建立网络握手
    const rawConn = await openSocket(config.host, credentials.token);

    // 3. 握手完成后再调用内部构造函数返回实例
    return new DatabaseConnection(rawConn, credentials);
  }

  query(sql) {
    return this.conn.execute(sql);
  }
}

// 模拟异步辅助函数
async function fetchRemoteAuth(url) {
  return { token: 'auth_tok_xyz123' };
}
async function openSocket(host, token) {
  return { execute: (s) => `执行结果: ${s} (主机: ${host})` };
}

// 调用方式:优雅解决异步初始化问题
(async () => {
  const db = await DatabaseConnection.create({
    host: 'db.internal.net',
    authUrl: 'https://auth.internal.net/get'
  });
  console.log(db.query('SELECT * FROM users'));
})();
提示:通过将异步过程封装在 static async create() 中,既保证了返回的实例是完全初始化完毕的健康对象,又避免了调用方在创建对象后还要手动调用一次 instance.init(),规避了状态不一致隐患。

五、工程实践中的避坑指南

在 ES6 Class 中落地工厂模式时,需要特别注意以下细节,避免引入不必要的维护负担:

潜在隐患 现象与风险 最佳应对策略
过度设计与模式滥用 只有一两个稳定且构造极简的类,却套用三层工厂结构。 单一类型或无需多态时直接使用 new Class(),无需强制引入工厂。
缺乏接口契约检查 由于 JavaScript 没有编译期 interface,注册了缺少公共方法的类。 在 factory.register() 阶段检查传入类原型是否包含约定方法,及时报错。
未处理未知类型异常 传入无效类型名时返回 undefined,导致后续链式调用爆出“cannot read properties of undefined”。 在工厂内部严格校验并显式抛出语义明确的 Error,方便排查定位。
单例与多例混淆 将带状态的类在工厂中缓存并重复返回,导致不同请求间数据相互污染。 工厂默认应创建全新实例;若需单例,显式使用专用缓存字典并明确命名方法。
怎么在ES6 Class中实现JavaScript工厂模式
图 4:ES6 工厂模式应用选型与决策指南

六、总结

在 ES6 Class 中实现工厂模式的关键不是死记模式术语,而是利用 JavaScript 类的语法特性为对象生命周期建立清晰的边界:

  • 对于日常项目中的类型分发场景,优先采用静态方法配合注册表(Static Factory + Map),能以极小的代码成本带来优秀的解耦与扩展性;
  • 对于涉及网络鉴权、配置拉取等前置任务的对象,使用静态异步工厂方法(static async create())是规避 constructor 语法限制的最佳实践;
  • 始终牢记接口统一性:工厂产出的所有子类实例应遵循相同的公开方法约定,使调用方无需关注具体实现类型,真正发挥多态代码的简洁与可靠优势。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章