目录

1.简单工厂

1. 铺垫:构造器模式——解决“重复写对象”的烦恼

问题场景

构造器的解法

核心逻辑

2. 重点:简单工厂模式——解决“选构造器太麻烦”的烦恼

新问题:职业不同,个性需求也不同

工厂模式的解法:把“判断+创建”封装成一个“工厂函数”

工厂模式的核心优势

一句话记住核心思想

什么时候用工厂模式?

3 案例练习(贴近生活,一看就懂)

案例1:外卖平台——创建不同类型的订单

需求场景

案例解析

案例2:办公软件——创建不同类型的文档

需求场景

案例解析

2. 抽象工厂模式

1 简单工厂的问题(以员工系统为例)

2. 抽象工厂模式核心:四大角色+实战实例

四大核心角色(必记)

手机工厂

关键优势(符合开放封闭原则)

抽象工厂 vs 简单工厂

学习抽象工厂的3个核心价值

3. 两个前端实战练习案例

案例1:UI组件库的“主题工厂”(产品族:按钮+输入框)

需求

要求

案例2:接口请求的“环境工厂”(产品族:GET请求+POST请求)

需求

要求


1.简单工厂

文章围绕“怎么更轻松地创建对象”展开,核心是帮你解决「手动创建相似对象太麻烦」的问题,主要讲了两个关键知识点:构造器模式(基础)和简单工厂模式(进阶),最终落脚点是“分离代码中的‘变’与‘不变’”。

1. 铺垫:构造器模式——解决“重复写对象”的烦恼

问题场景

假如你要做一个员工信息系统,一开始只有1个员工,手动写对象没问题:

// 1个员工:简单
const liLei = { name: '李雷', age: 25, career: '程序员' };

但如果有500个员工,手动写500个类似对象?累死!

构造器的解法

用「构造函数」把“员工的共性”(都有name/age/career)封装起来,需要新员工时只需传“个性值”(具体名字/年龄):

// 封装共性:所有员工都有这3个属性
function User(name, age, career) {
  this.name = name; // 个性:每个人名字不同
  this.age = age;   // 个性:每个人年龄不同
  this.career = career; // 个性:每个人职业不同
}

// 创建员工:只需传“个性值”,不用重复写属性名
const hanMeiMei = new User('韩梅梅', 24, '产品经理');
const boss = new User('王总', 40, '老板');
核心逻辑
  • 不变:员工必须有的属性(name/age/career);
  • 变:每个属性的具体值;
  • 作用:把“重复的属性结构”封装起来,减少代码冗余。

2. 重点:简单工厂模式——解决“选构造器太麻烦”的烦恼

新问题:职业不同,个性需求也不同

老板说:“程序员要显示工作职责(写代码),产品经理也要显示(写PRD)”。如果继续用构造器,得为每个职业写一个构造函数:

// 程序员构造器:多了work属性
function Coder(name, age) {
  this.name = name;
  this.age = age;
  this.career = '程序员';
  this.work = ['写代码', '修Bug'];
}

// 产品经理构造器:多了work属性,但值不同
function ProductManager(name, age) {
  this.name = name;
  this.age = age;
  this.career = '产品经理';
  this.work = ['写PRD', '催更'];
}

但问题来了:每次创建员工,都要手动判断“他是程序员还是产品”,再选对应的构造器——500个员工要判断500次?太麻烦!

工厂模式的解法:把“判断+创建”封装成一个“工厂函数”

你不用管“选哪个构造器”,只需给工厂传“名字、年龄、职业”,工厂自动帮你搞定:

// 第一步:先抽“员工共性”到User构造器(所有员工都有这4个属性)
function User(name, age, career, work) {
  this.name = name;
  this.age = age;
  this.career = career;
  this.work = work; // 现在work也成了共性属性,只是值不同
}

// 第二步:写“工厂函数”——负责判断职业、生成work值、创建员工
function UserFactory(name, age, career) {
  let work; // 个性:不同职业的work不同
  switch(career) {
    case '程序员':
      work = ['写代码', '修Bug'];
      break;
    case '产品经理':
      work = ['写PRD', '催更'];
      break;
    case '老板':
      work = ['喝茶', '见客户'];
      break;
  }
  // 统一用User构造器创建对象,返回结果
  return new User(name, age, career, work);
}

// 第三步:创建员工——只需传3个参数,不用管判断逻辑!
const liLei = UserFactory('李雷', 25, '程序员');
const boss = UserFactory('王总', 40, '老板');
工厂模式的核心优势
  • 你不用关心“对象怎么造”(不用想选哪个构造器、work值怎么写),只需关心“传什么参数”——像点外卖一样,点了就等上菜;
  • 后续加新职业(如“设计师”),只需在工厂函数里加一个case,不用改其他代码,很灵活。

一句话记住核心思想

  • 所有模式的目的:把“不变的部分”固定住,让“变化的部分”灵活动
  • 构造器模式:固定“对象的属性结构”(不变),让“属性值”灵活(变);
  • 简单工厂模式:固定“对象创建流程”(不变),让“职业对应的个性配置”灵活(变)。

什么时候用工厂模式?

  • 当你需要创建“多个相似但有细微差异的对象”(如不同职业的员工、不同类型的商品);
  • 当你发现“手动选构造器、写重复逻辑太麻烦”时,就用工厂把这些麻烦事包起来。

3 案例练习(贴近生活,一看就懂)

案例1:外卖平台——创建不同类型的订单
需求场景

外卖平台有两种订单:「餐饮订单」和「生鲜订单」:

  • 共性:所有订单都有「订单号、用户手机号、收货地址」;
  • 个性:
    • 餐饮订单:需要「口味备注」(如“少辣”);
    • 生鲜订单:需要「保鲜要求」(如“冷藏配送”);
  • 要求:不用手动判断订单类型,传参数就能生成对应订单。
// 1. 抽订单共性:所有订单都有的属性
function Order(orderId, phone, address, extra) {
  this.orderId = orderId;   // 不变:每个订单都有订单号
  this.phone = phone;       // 不变:每个订单都有手机号
  this.address = address;   // 不变:每个订单都有地址
  this.extra = extra;       // 变:不同订单的额外信息(口味/保鲜)
}

// 2. 订单工厂:处理“个性”,创建订单
function OrderFactory(orderId, phone, address, orderType, extraInfo) {
  let extra; // 存储个性信息
  switch(orderType) {
    case '餐饮':
      extra = { tasteNote: extraInfo }; // 餐饮的个性:口味备注
      break;
    case '生鲜':
      extra = { freshReq: extraInfo };  // 生鲜的个性:保鲜要求
      break;
  }
  // 统一创建订单并返回
  return new Order(orderId, phone, address, extra);
}

// 3. 测试:只需传参数,不用管判断逻辑
const foodOrder = OrderFactory(
  '123456',        // 订单号
  '13800138000',   // 手机号
  'XX小区1号楼',   // 地址
  '餐饮',          // 订单类型
  '少辣、多放醋'   // 餐饮个性信息
);

const freshOrder = OrderFactory(
  '654321',
  '13900139000',
  'YY小区3号楼',
  '生鲜',
  '冷藏配送、避免挤压'
);

console.log(foodOrder.extra.tasteNote); // 少辣、多放醋
console.log(freshOrder.extra.freshReq); // 冷藏配送、避免挤压
案例解析
  • 不变:订单的基础结构(orderId/phone/address);
  • 变:不同订单类型的额外信息(口味/保鲜);
  • 工厂的价值:如果后续加“鲜花订单”(需“花期要求”),只需在工厂加一个case,不用改Order构造器——灵活!
案例2:办公软件——创建不同类型的文档
需求场景

办公软件能创建3种文档:「Word文档」「Excel表格」「PPT演示」:

  • 共性:所有文档都有「文档名、创建时间、作者」;
  • 个性:
    • Word:有「字数统计」;
    • Excel:有「表格行数」;
    • PPT:有「幻灯片页数」;
  • 要求:传文档类型和个性参数,自动生成文档。
// 1. 抽文档共性:所有文档都有的属性
function Document(docName, author, createTime, spec) {
  this.docName = docName;   // 不变:文档名
  this.author = author;     // 不变:作者
  this.createTime = createTime; // 不变:创建时间
  this.spec = spec;         // 变:不同文档的特殊属性(字数/行数/页数)
}

// 2. 文档工厂:处理个性,创建文档
function DocumentFactory(docName, author, docType, specValue) {
  const createTime = new Date().toLocaleString(); // 统一生成当前时间(不变)
  let spec; // 存储个性属性
  switch(docType) {
    case 'Word':
      spec = { wordCount: specValue }; // Word个性:字数
      break;
    case 'Excel':
      spec = { rowCount: specValue };  // Excel个性:行数
      break;
    case 'PPT':
      spec = { slideCount: specValue }; // PPT个性:页数
      break;
  }
  // 统一创建文档并返回
  return new Document(docName, author, createTime, spec);
}

// 3. 测试:快速创建不同文档
const reportDoc = DocumentFactory(
  '季度报告', 
  '张三', 
  'Word', 
  2500 // Word文档字数
);

const dataSheet = DocumentFactory(
  '销售数据', 
  '李四', 
  'Excel', 
  120 // Excel表格行数
);

console.log(reportDoc.createTime); // 自动生成的当前时间(如2025/9/28 15:30:00)
console.log(dataSheet.spec.rowCount); // 120
案例解析
  • 不变:文档的基础信息+创建时间生成逻辑;
  • 变:不同文档的特殊属性(字数/行数/页数);
  • 工厂的价值:业务层不用关心“时间怎么生成”“属性怎么匹配”,传参数就完事——简化开发!

如果后续需求变复杂(比如外卖平台要区分“普通订单”和“会员订单”,且会员订单有额外折扣),你觉得现在的简单工厂模式还够用吗?如果不够,应该怎么优化?(提示:可以想想“抽象工厂模式”,后续我们可以深入聊~)

为了帮你清晰掌握文章核心,我会先以“问题引入→模式拆解→价值总结”的逻辑梳理内容,结合实例简化抽象概念,再设计两个贴近前端场景的练习案例,确保深入浅出。

2. 抽象工厂模式

  • 争议点:JS是弱类型语言,天然支持多态,且无原生抽象类,有人认为抽象工厂对前端无用。
  • 核心观点:抽象工厂的价值不在“频繁使用”,而在理解设计原则(如开放封闭)、拓宽技术视野,且是面试高频考点。

1 简单工厂的问题(以员工系统为例)

function Factory(name, age, career) {
  let work;
  switch(career) {
    case 'coder': work = ['写代码']; break;
    case 'boss': work = ['喝茶']; break; // Boss与普通员工逻辑混在一起
      // 新增工种需修改switch内部,违反开放封闭原则
  }
  return new User(name, age, career, work);
}
  • 致命缺陷
    1. 所有工种逻辑耦合在一个工厂,工厂会越来越庞大,维护风险高;
    2. 新增工种(如“外包”“保安”)需修改工厂内部代码,违反“对拓展开放、对修改封闭”原则;
    3. 不同类型用户(如Boss有特殊权限)无法单独处理,逻辑混乱。

2. 抽象工厂模式核心:四大角色+实战实例

抽象工厂的本质是“用抽象类约定产品族的生产规则,具体工厂实现规则”,解决简单工厂的耦合问题。

四大核心角色(必记)

角色

定义

文章实例(手机工厂)

抽象工厂(抽象类)

约定“产品族”的生产能力(如手机需生产“操作系统+硬件”),不能直接实例化

MobilePhoneFactory(含createOS/createHardWare

具体工厂

继承抽象工厂,实现具体产品的生产(如生产“安卓+高通”手机)

FakeStarFactory(生产安卓系统+高通硬件)

抽象产品(抽象类)

约定某类产品的共性功能(如所有操作系统都能“控制硬件”)

OS(含controlHardWare)、HardWare(含operateByOrder

具体产品

继承抽象产品,实现具体功能(如安卓系统的控制逻辑)

AndroidOSQualcommHardWare

手机工厂

class MobilePhoneFactory { // 抽象工厂:手机需生产OS和硬件
  createOS() { throw new Error('需重写createOS'); }
  createHardWare() { throw new Error('需重写createHardWare'); }
}
// 抽象产品1:操作系统
class OS {
  controlHardWare() { throw new Error('需重写controlHardWare'); }
}
// 抽象产品2:硬件
class HardWare {
  operateByOrder() { throw new Error('需重写operateByOrder'); }
}
// 具体产品:安卓系统
class AndroidOS extends OS {
  controlHardWare() { console.log('安卓方式控制硬件'); }
}
// 具体产品:高通硬件
class QualcommHardWare extends HardWare {
  operateByOrder() { console.log('高通方式运转'); }
}
class FakeStarFactory extends MobilePhoneFactory { // 生产“安卓+高通”手机
  createOS() { return new AndroidOS(); } // 生产具体OS
  createHardWare() { return new QualcommHardWare(); } // 生产具体硬件
}
const myPhone = new FakeStarFactory();
const myOS = myPhone.createOS();
const myHardWare = myPhone.createHardWare();
myOS.controlHardWare(); // 输出:安卓方式控制硬件
myHardWare.operateByOrder(); // 输出:高通方式运转
关键优势(符合开放封闭原则)

若需新增“苹果手机工厂”,只需新增具体工厂,无需修改原有代码:

class ApplePhoneFactory extends MobilePhoneFactory {
  createOS() { return new AppleOS(); } // 新增苹果OS具体产品
  createHardWare() { return new AppleHardWare(); } // 新增苹果硬件具体产品
}

抽象工厂 vs 简单工厂

对比维度

简单工厂

抽象工厂

处理场景

单一产品(如仅创建“用户”)

产品族(如“操作系统+硬件”)

耦合度

高(所有逻辑在一个工厂)

低(按产品族拆分工厂)

开放封闭原则

违反(新增产品需改工厂内部)

符合(新增产品族只需加工厂)

适用复杂度

简单场景(少量产品)

复杂场景(多产品族、多扩展)

学习抽象工厂的3个核心价值

  1. 拓宽技术视野:理解强类型语言(如Java)的设计思路,适配JS未来趋势(如TypeScript);
  2. 应对面试需求:抽象工厂是设计模式高频考点,尤其针对有后端背景的面试官;
  3. 深化设计原则:是“开放封闭原则”的最佳实践案例,为后续学习其他模式打基础。

3. 两个前端实战练习案例

案例1:UI组件库的“主题工厂”(产品族:按钮+输入框)

需求

设计一个支持“暗黑主题”和“亮色主题”的组件工厂,每个主题需包含“按钮(Button)”和“输入框(Input)”两种组件,且组件样式与交互匹配主题。

要求

  1. 定义抽象工厂ThemeFactory(约定生产createButtoncreateInput);
  2. 定义抽象产品Button(含render方法)和Input(含render方法);
  3. 实现具体工厂DarkThemeFactoryLightThemeFactory
  4. 实现具体产品DarkButtonLightButtonDarkInputLightInput

// 1. 抽象工厂:主题需生产按钮和输入框
class ThemeFactory {
  createButton() { throw new Error('需重写createButton'); }
  createInput() { throw new Error('需重写createInput'); }
}

// 2. 抽象产品:按钮和输入框
class Button {
  render() { throw new Error('需重写render'); }
}
class Input {
  render() { throw new Error('需重写render'); }
}

// 3. 具体产品:暗黑主题组件
class DarkButton extends Button {
  render() { console.log('渲染黑色按钮,文字白色'); }
}
class DarkInput extends Input {
  render() { console.log('渲染黑色输入框,文字白色'); }
}

// 4. 具体产品:亮色主题组件
class LightButton extends Button {
  render() { console.log('渲染白色按钮,文字黑色'); }
}
class LightInput extends Input {
  render() { console.log('渲染白色输入框,文字黑色'); }
}

// 5. 具体工厂:暗黑主题工厂
class DarkThemeFactory extends ThemeFactory {
  createButton() { return new DarkButton(); }
  createInput() { return new DarkInput(); }
}

// 6. 具体工厂:亮色主题工厂
class LightThemeFactory extends ThemeFactory {
  createButton() { return new LightButton(); }
  createInput() { return new LightInput(); }
}

// 使用:渲染暗黑主题组件
const darkTheme = new DarkThemeFactory();
darkTheme.createButton().render(); // 黑色按钮
darkTheme.createInput().render(); // 黑色输入框

若新增“复古主题”,需修改哪些代码?(仅需新增VintageThemeFactory和对应的VintageButton/VintageInput,无需改原有代码)

案例2:接口请求的“环境工厂”(产品族:GET请求+POST请求)

需求

设计一个支持“开发环境(dev)”和“生产环境(prod)”的请求工厂,每个环境的请求基础URL不同(dev:http://dev.api.com,prod:http://prod.api.com),且需提供GETPOST两种请求方法。

要求

  1. 定义抽象工厂RequestFactory(约定生产createGetcreatePost);
  2. 定义抽象产品GetRequest(含send方法)和PostRequest(含send方法);
  3. 实现具体工厂DevRequestFactoryProdRequestFactory
  4. 调用具体工厂发送请求,验证URL是否正确。
// 1. 抽象工厂:请求环境需生产GET和POST请求
class RequestFactory {
  createGet(url) { throw new Error('需重写createGet'); }
  createPost(url, data) { throw new Error('需重写createPost'); }
}

// 2. 抽象产品:GET和POST请求
class GetRequest {
  constructor(baseUrl, url) {
    this.fullUrl = `${baseUrl}${url}`;
  }
  send() { throw new Error('需重写send'); }
}
class PostRequest {
  constructor(baseUrl, url, data) {
    this.fullUrl = `${baseUrl}${url}`;
    this.data = data;
  }
  send() { throw new Error('需重写send'); }
}

// 3. 具体产品:开发环境请求
class DevGetRequest extends GetRequest {
  send() { console.log(`Dev环境GET请求:${this.fullUrl}`); }
}
class DevPostRequest extends PostRequest {
  send() { console.log(`Dev环境POST请求:${this.fullUrl},数据:${JSON.stringify(this.data)}`); }
}

// 4. 具体产品:生产环境请求
class ProdGetRequest extends GetRequest {
  send() { console.log(`Prod环境GET请求:${this.fullUrl}`); }
}
class ProdPostRequest extends PostRequest {
  send() { console.log(`Prod环境POST请求:${this.fullUrl},数据:${JSON.stringify(this.data)}`); }
}

// 5. 具体工厂:开发环境工厂
class DevRequestFactory extends RequestFactory {
  createGet(url) { return new DevGetRequest('http://dev.api.com', url); }
  createPost(url, data) { return new DevPostRequest('http://dev.api.com', url, data); }
}

// 6. 具体工厂:生产环境工厂
class ProdRequestFactory extends RequestFactory {
  createGet(url) { return new ProdGetRequest('http://prod.api.com', url); }
  createPost(url, data) { return new ProdPostRequest('http://prod.api.com', url, data); }
}

// 使用:开发环境发送GET请求
const devRequest = new DevRequestFactory();
devRequest.createGet('/user').send(); // 输出:Dev环境GET请求:http://dev.api.com/user
// 抽象工厂(TypeScript 版本)
// - 提供两组产品示例:主题组件(Button / Input)和请求(GET / POST)
// - 使用抽象类/接口保证类型安全

// ==================== 主题工厂 ====================
export abstract class ThemeFactory {
  abstract createButton(): Button;
  abstract createInput(): Input;
}

export abstract class Button {
  abstract render(): void;
}

export abstract class Input {
  abstract render(): void;
}

export class DarkButton extends Button {
  render(): void {
    console.log('渲染黑色按钮,文字白色');
  }
}

export class DarkInput extends Input {
  render(): void {
    console.log('渲染黑色输入框,文字白色');
  }
}

export class LightButton extends Button {
  render(): void {
    console.log('渲染白色按钮,文字黑色');
  }
}

export class LightInput extends Input {
  render(): void {
    console.log('渲染白色输入框,文字黑色');
  }
}

export class DarkThemeFactory extends ThemeFactory {
  createButton(): Button {
    return new DarkButton();
  }
  createInput(): Input {
    return new DarkInput();
  }
}

export class LightThemeFactory extends ThemeFactory {
  createButton(): Button {
    return new LightButton();
  }
  createInput(): Input {
    return new LightInput();
  }
}

// ==================== 请求工厂 ====================
// 抽象工厂:生成环境相关的请求对象
export abstract class RequestFactory {
  abstract createGet(url: string): GetRequest;
  abstract createPost<T = unknown>(url: string, data: T): PostRequest<T>;
}

export abstract class GetRequest {
  protected fullUrl: string;
  constructor(baseUrl: string, url: string) {
    this.fullUrl = `${baseUrl}${url}`;
  }
  abstract send(): void;
}

export abstract class PostRequest<T = unknown> {
  protected fullUrl: string;
  protected data: T;
  constructor(baseUrl: string, url: string, data: T) {
    this.fullUrl = `${baseUrl}${url}`;
    this.data = data;
  }
  abstract send(): void;
}

export class DevGetRequest extends GetRequest {
  send(): void {
    console.log(`Dev环境GET请求:${this.fullUrl}`);
  }
}

export class DevPostRequest<T = unknown> extends PostRequest<T> {
  send(): void {
    console.log(`Dev环境POST请求:${this.fullUrl},数据:${JSON.stringify(this.data)}`);
  }
}

export class ProdGetRequest extends GetRequest {
  send(): void {
    console.log(`Prod环境GET请求:${this.fullUrl}`);
  }
}

export class ProdPostRequest<T = unknown> extends PostRequest<T> {
  send(): void {
    console.log(`Prod环境POST请求:${this.fullUrl},数据:${JSON.stringify(this.data)}`);
  }
}

export class DevRequestFactory extends RequestFactory {
  private baseUrl = 'http://dev.api.com';
  createGet(url: string): GetRequest {
    return new DevGetRequest(this.baseUrl, url);
  }
  createPost<T = unknown>(url: string, data: T): PostRequest<T> {
    return new DevPostRequest<T>(this.baseUrl, url, data);
  }
}

export class ProdRequestFactory extends RequestFactory {
  private baseUrl = 'http://prod.api.com';
  createGet(url: string): GetRequest {
    return new ProdGetRequest(this.baseUrl, url);
  }
  createPost<T = unknown>(url: string, data: T): PostRequest<T> {
    return new ProdPostRequest<T>(this.baseUrl, url, data);
  }
}

// ==================== 演示 / 使用 ====================
export function demoTheme(): void {
	const darkTheme = new DarkThemeFactory();
	darkTheme.createButton().render();
	darkTheme.createInput().render();

	const lightTheme = new LightThemeFactory();
	lightTheme.createButton().render();
	lightTheme.createInput().render();
}

export function demoRequest(): void {
	const devFactory = new DevRequestFactory();
	devFactory.createGet('/user').send();
	devFactory.createPost('/user', { name: '张三' }).send();

	const prodFactory = new ProdRequestFactory();
	prodFactory.createGet('/health').send();
	prodFactory.createPost('/log', { event: 'start' }).send();
}

// 运行 demo(直接执行此文件时)
console.log('--- Theme Demo ---');
demoTheme();
console.log('\n--- Request Demo ---');
demoRequest();

若新增“测试环境(test)”,只需新增TestRequestFactory和对应的请求产品,完全符合开放封闭原则。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐