一、 什么是工厂模式?
工厂模式(Factory Pattern)是最常用的设计模式之一,它提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。
工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。
通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。
二、工厂模式的类型
2.1 简单工厂模式(Simple Factory Pattern) :
- 简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。
2.2 工厂方法模式(Factory Method Pattern) :
- 工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。
2.3 抽象工厂模式(Abstract Factory Pattern) :
- 抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
三、 工厂方法模式
工厂方法模式定义了一个用于创建对象的接口 Creator
(创建者),但让子类 ConcreteCreator
(具体创建者) 决定实例化哪一个类 ConcreteProduct
(具体产品)。工厂方法使一个类的实例化延迟到其子类。
示例: 未使用工厂方法之前
typescript
// 1. 产品接口 (IShape)
interface IShape {
draw(): void;
}
// 2. 具体产品 (Concrete Products)
class Circle implements IShape {
constructor(public radius: number) {}
draw(): void {
console.log(`绘制圆形,半径:${this.radius}`);
}
}
class Rectangle implements IShape {
constructor(public width: number, public height: number) {}
draw(): void {
console.log(`绘制矩形,宽:${this.width},高:${this.height}`);
}
}
class Triangle implements IShape {
constructor(public side1: number, public side2: number, public side3: number) {}
draw(): void {
console.log(`绘制三角形,边长:${this.side1},${this.side2},${this.side3}`);
}
}
// 3. 客户端代码 (创建逻辑与使用耦合)
type ShapeType = 'circle' | 'rectangle' | 'triangle' | 'pentagon';
function createAndDrawShape(type: ShapeType, ...args: number[]): void {
let shape: IShape | null = null;
// 痛点所在:大量的 if-else 或 switch-case
if (type === 'circle') {
shape = new Circle(args[0]!);
} else if (type === 'rectangle') {
shape = new Rectangle(args[0]!, args[1]!);
} else if (type === 'triangle') {
shape = new Triangle(args[0]!, args[1]!, args[2]!);
} else {
throw new Error('不支持的图形类型!');
}
if (shape) {
shape.draw();
}
}
// 使用
createAndDrawShape('circle', 5);
createAndDrawShape('rectangle', 10, 20);
createAndDrawShape('triangle', 3, 4, 5);
createAndDrawShape('pentagon', 5); // 编译通过,运行时报错
使用工厂模式
1 定义抽象工厂 (Creator)
一个定义了工厂方法的接口或抽象类,它负责声明生产产品的方法。
typescript
// 4.1 抽象工厂接口 (Creator)
interface IShapeFactory {
createShape(...args: number[]): IShape; // 这就是"工厂方法"
}
2 为每个具体产品创建具体工厂 (Concrete Creator) 现在,我们不再需要一个大而全的 createAndDrawShape
函数。每种图形都有自己专属的、能创建自己的工厂。
typescript
// 4.2 具体工厂 (Concrete Creators)
class CircleFactory implements IShapeFactory {
createShape(...args: number[]): IShape {
console.log('圆形工厂:创建圆形中...');
// 这里可以封装复杂的圆形创建和初始化逻辑
return new Circle(args[0]!);
}
}
class RectangleFactory implements IShapeFactory {
createShape(...args: number[]): IShape {
console.log('矩形工厂:创建矩形中...');
// 这里可以封装复杂的矩形创建和初始化逻辑
return new Rectangle(args[0]!, args[1]!);
}
}
class TriangleFactory implements IShapeFactory {
createShape(...args: number[]): IShape {
console.log('三角形工厂:创建三角形中...');
// 这里可以封装复杂的三角形创建和初始化逻辑
return new Triangle(args[0]!, args[1]!, args[2]!);
}
}
3 改造客户端代码 现在,客户端不再直接 new
具体图形,也不再需要庞大的 if-else
。它只需要知道它需要哪种工厂,然后通过工厂去创建产品。
typescript
// 5. 客户端代码 (通过工厂来创建和使用)
function clientCode(factory: IShapeFactory, ...args: number[]): void {
const shape = factory.createShape(...args); // 客户端只通过工厂接口来创建对象
shape.draw();
}
// 使用工厂方法模式
console.log('\n--- 使用工厂方法模式 ---');
const circleFactory = new CircleFactory();
clientCode(circleFactory, 5); // 客户端传入具体的工厂实例
const rectangleFactory = new RectangleFactory();
clientCode(rectangleFactory, 10, 20);
const triangleFactory = new TriangleFactory();
clientCode(triangleFactory, 3, 4, 5);
// 新增一个 Pentagon 图形
// 我们只需要:
// 1. 新增 Pentagon 类实现 IShape
// 2. 新增 PentagonFactory 实现 IShapeFactory
// 无需修改任何已有的工厂或 clientCode 函数!
class Pentagon implements IShape {
constructor(public sideLength: number) {}
draw(): void {
console.log(`绘制五边形,边长:${this.sideLength}`);
}
}
class PentagonFactory implements IShapeFactory {
createShape(...args: number[]): IShape {
console.log('五边形工厂:创建五边形中...');
return new Pentagon(args[0]!);
}
}
console.log('\n--- 扩展新图形:五边形 ---');
const pentagonFactory = new PentagonFactory();
clientCode(pentagonFactory, 8); // 轻松扩展,无需改动旧代码!
解析:
- 通过工厂方法模式,我们实现了创建逻辑与使用逻辑的解耦,大大提升了代码的可维护性和扩展性。新增一个图形,只需要新增一个类和一个工厂,无需改动已有的代码!
四、 工厂方法模式的核心角色
为了更清晰地理解其结构,我们通常将工厂方法模式分解为以下几个核心角色:
- Product (抽象产品) :定义了工厂方法所创建的对象的接口。
- 在我们的例子中是
IShape
。
- 在我们的例子中是
- ConcreteProduct (具体产品) :实现了 Product 接口的具体类。
- 例如
Circle
,Rectangle
,Triangle
,Pentagon
。
- 例如
- Creator (抽象工厂) :声明了工厂方法,该方法返回一个 Product 对象。它是工厂方法模式的核心。
- 在我们的例子中是
IShapeFactory
。
- 在我们的例子中是
- ConcreteCreator (具体工厂) :重写抽象工厂中的工厂方法,以返回一个
ConcreteProduct
实例。- 例如
CircleFactory
,RectangleFactory
,TriangleFactory
,PentagonFactory
。
- 例如
五、抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式旨在解决创建多个产品等级结构中的相关产品组合的问题。
你现在不仅需要创建图形(IShape
),还需要创建文本框(ITextBox
)。更复杂的是,你的应用需要支持多种主题(例如:LightTheme 和 DarkTheme)。
- Light 主题下,你需要
LightCircle
、LightSquare
、LightTextBox
。 - Dark 主题下,你需要
DarkCircle
、DarkSquare
、DarkTextBox
。
这时候,如果用工厂方法模式,你需要 LightCircleFactory
、DarkCircleFactory
、LightSquareFactory
、DarkSquareFactory
等一系列工厂,管理起来会非常混乱。而且,你很难保证一个主题下的所有产品都使用统一的视觉风格。这个时候就可以使用抽象工厂模式,把每个主题作为一个组合下的创建图形和创建文本框封装成一个抽象工厂接口。
typescript
// 1. 抽象工厂 (AbstractFactory)
// 定义一个接口,用于创建一系列相关产品(一个产品组合)
interface IUIFactory {
createShape(): IShape;
createTextBox(): ITextBox;
// ...还可以有 createButton(), createSlider() 等
}
六、工厂方法 vs. 抽象工厂:核心对比
特性 | 工厂方法模式 (Factory Method) | 抽象工厂模式 (Abstract Factory) |
---|---|---|
解决问题 | 创建单一产品等级结构中的对象。 | 创建多个产品等级结构 中的相关或依赖对象组合。 |
创建对象数量 | 每个具体工厂通常只创建一个具体产品。 | 每个具体工厂可以创建一整套相关的具体产品 (一个组合)。 |
关注点 | 如何为特定产品创建具体实例 (一个工厂一个产品)。 | 如何创建"产品组合" (一个工厂一组产品)。 |
扩展性 1 (新增产品类型) | 好:新增产品类型时,需添加新的具体产品类和新的具体工厂类。 | 差 :新增产品类型(比如新增 ISlider )时,需要修改抽象工厂接口和所有具体工厂类。 |
扩展性 2 (新增产品组合) | 不适用/差:无法很好地处理产品组合切换问题。 | 好:新增一个产品组合(如新增主题)时,只需添加新的具体工厂类和具体产品类。无需修改抽象工厂接口和客户端。 |
实现方式 | 继承。抽象工厂由子类实现具体产品的创建。 | 组合。抽象工厂由客户端选择具体的工厂接口,该接口包含多个创建方法。 |
类图特点 | 一个抽象工厂 + 多个具体工厂,每个工厂有自己的工厂方法。 | 一个抽象工厂 + 多个具体工厂,每个工厂有多个工厂方法(每个方法创建一个不同等级的产品)。 |
何时使用 | 当你的类不知道它要创建哪个具体类的对象,或者希望子类来指定要创建的对象时。 | 当一个系统需要独立于其产品的创建、组合和表示时。当一个系统要由多个产品组合中的一个来配置时。 |
为了方便大家学习和实践,本文的所有示例代码和完整项目结构都已整理上传至我的 GitHub 仓库。欢迎大家克隆、研究、提出 Issue,共同进步!
📂 核心代码与完整示例: GoF
总结
如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript/TypeScript开发干货