设计模式-1(简单工厂与抽象工厂)
目录
案例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);
}
- 致命缺陷:
-
- 所有工种逻辑耦合在一个工厂,工厂会越来越庞大,维护风险高;
- 新增工种(如“外包”“保安”)需修改工厂内部代码,违反“对拓展开放、对修改封闭”原则;
- 不同类型用户(如Boss有特殊权限)无法单独处理,逻辑混乱。
2. 抽象工厂模式核心:四大角色+实战实例
抽象工厂的本质是“用抽象类约定产品族的生产规则,具体工厂实现规则”,解决简单工厂的耦合问题。
四大核心角色(必记)
| 角色 | 定义 | 文章实例(手机工厂) |
| 抽象工厂(抽象类) | 约定“产品族”的生产能力(如手机需生产“操作系统+硬件”),不能直接实例化 |
|
| 具体工厂 | 继承抽象工厂,实现具体产品的生产(如生产“安卓+高通”手机) |
|
| 抽象产品(抽象类) | 约定某类产品的共性功能(如所有操作系统都能“控制硬件”) |
|
| 具体产品 | 继承抽象产品,实现具体功能(如安卓系统的控制逻辑) |
|
手机工厂
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个核心价值
- 拓宽技术视野:理解强类型语言(如Java)的设计思路,适配JS未来趋势(如TypeScript);
- 应对面试需求:抽象工厂是设计模式高频考点,尤其针对有后端背景的面试官;
- 深化设计原则:是“开放封闭原则”的最佳实践案例,为后续学习其他模式打基础。
3. 两个前端实战练习案例
案例1:UI组件库的“主题工厂”(产品族:按钮+输入框)
需求
设计一个支持“暗黑主题”和“亮色主题”的组件工厂,每个主题需包含“按钮(Button)”和“输入框(Input)”两种组件,且组件样式与交互匹配主题。
要求
- 定义抽象工厂
ThemeFactory(约定生产createButton和createInput); - 定义抽象产品
Button(含render方法)和Input(含render方法); - 实现具体工厂
DarkThemeFactory和LightThemeFactory; - 实现具体产品
DarkButton、LightButton、DarkInput、LightInput。
// 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),且需提供GET和POST两种请求方法。
要求
- 定义抽象工厂
RequestFactory(约定生产createGet和createPost); - 定义抽象产品
GetRequest(含send方法)和PostRequest(含send方法); - 实现具体工厂
DevRequestFactory和ProdRequestFactory; - 调用具体工厂发送请求,验证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和对应的请求产品,完全符合开放封闭原则。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)