壹、简单工厂、工厂
一、简单工厂模式(Simple Factory)
1. 核心定义
- 本质:由一个工厂类根据传入的参数,动态决定创建哪一种产品类的实例。
- 特点:
- 将对象创建逻辑集中管理
- 客户端无需知道具体产品类名,只需知道参数
- 违反 开闭原则(新增产品需修改工厂类)
2. 代码示例(TypeScript)
// 抽象产品
interface Phone {
call(): void;
}
// 具体产品
class IPhone implements Phone {
call() { console.log("Using iPhone call"); }
}
class AndroidPhone implements Phone {
call() { console.log("Using Android call"); }
}
// 简单工厂
class PhoneFactory {
public static createPhone(type: string): Phone {
switch (type) {
case "iphone":
return new IPhone();
case "android":
return new AndroidPhone();
default:
throw new Error("Invalid phone type");
}
}
}
// 客户端使用
const phone = PhoneFactory.createPhone("iphone");
phone.call(); // Output: Using iPhone call
3. UML 类图
classDiagram
class Phone {
<<interface>>
+call()
}
class IPhone {
+call()
}
class AndroidPhone {
+call()
}
class PhoneFactory {
+createPhone() Phone
}
Phone <|-- IPhone
Phone <|-- AndroidPhone
PhoneFactory --> Phone : creates
二、工厂方法模式(Factory Method)
1. 核心定义
- 本质:定义一个创建对象的接口,但让子类决定实例化哪个类。
- 特点:
- 将对象创建延迟到子类
- 符合 开闭原则(新增产品只需添加新工厂)
- 需要更多类的引入
2. 代码示例(对比简单工厂)
// 抽象工厂
interface PhoneFactory {
createPhone(): Phone;
}
// 具体工厂
class IPhoneFactory implements PhoneFactory {
createPhone(): Phone { return new IPhone(); }
}
class AndroidPhoneFactory implements PhoneFactory {
createPhone(): Phone { return new AndroidPhone(); }
}
// 客户端使用
const factory: PhoneFactory = new IPhoneFactory();
const phone = factory.createPhone();
phone.call(); // Output: Using iPhone call
3. UML 类图
classDiagram
class Phone {
<<interface>>
+call()
}
class IPhone {
+call()
}
class AndroidPhone {
+call()
}
class PhoneFactory {
<<interface>>
+createPhone() Phone
}
class IPhoneFactory {
+createPhone() Phone
}
class AndroidPhoneFactory {
+createPhone() Phone
}
Phone <|-- IPhone
Phone <|-- AndroidPhone
PhoneFactory <|-- IPhoneFactory
PhoneFactory <|-- AndroidPhoneFactory
PhoneFactory --> Phone : creates
三、两种模式的核心区别
| 对比维度 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 创建逻辑位置 | 集中在单个工厂类 | 分散到各个子工厂类 |
| 扩展性 | 需修改工厂类(违反OCP) | 新增工厂子类即可(符合OCP) |
| 复杂度 | 类数量少,结构简单 | 类数量多,结构更复杂 |
| 适用场景 | 产品类型固定且少 | 产品类型可能频繁扩展 |
| 客户端依赖 | 依赖具体工厂类 | 依赖抽象工厂接口 |
四、实际应用场景选择
1. 选择 简单工厂 当:
- 产品种类较少且稳定(如全局的弹窗类型)
- 需要快速实现,不追求长期扩展性
- 典型应用:
- Vue 的
v-component动态组件 - 统一的 HTTP 客户端创建(根据配置返回 Axios/Fetch 实例)
- Vue 的
2. 选择 工厂方法 当:
- 产品系列需要灵活扩展(如多主题 UI 控件)
- 不同环境需要不同实现(如 Web/小程序组件)
- 典型应用:
- Vue Router 的路由组件懒加载
- 状态管理库的模块注册(Pinia/Vuex)
五、具体案例对比
场景:实现一个跨平台按钮组件
简单工厂实现
class ButtonFactory {
static createButton(platform: 'web' | 'mini') {
switch (platform) {
case 'web': return new WebButton();
case 'mini': return new MiniProgramButton();
}
}
}
// 问题:新增平台需修改工厂类
工厂方法实现
interface ButtonFactory {
createButton(): Button;
}
class WebButtonFactory implements ButtonFactory {
createButton() { return new WebButton(); }
}
class MiniProgramButtonFactory implements ButtonFactory {
createButton() { return new MiniProgramButton(); }
}
// 优势:新增平台只需添加新工厂类
六、在 Vue 中的体现
1. 简单工厂模式
v-component+component动态组件:<component :is="componentType" /> <!-- 根据字符串选择组件 -->- Vue 2 的
Vue.extend()创建子组件
2. 工厂方法模式
- 异步组件加载:
const AsyncComp = defineAsyncComponent(() => import('./Comp.vue')) - 自定义渲染器(
createRenderer)
七、总结决策图
graph TD
A[需要创建对象] --> B{产品类型是否频繁变化?}
B -->|否| C[使用简单工厂]
B -->|是| D[使用工厂方法]
C --> E[代码更简单]
D --> F[扩展性更好]
设计建议:
- 小型项目或稳定模块 → 简单工厂
- 大型项目或需要长期维护 → 工厂方法
贰、抽象产品为什么大多用接口、抽象工厂为什么大多用抽象类
一、抽象产品为什么用接口?
1. 核心目的
- 定义行为契约:接口强制所有具体产品实现统一的方法(如
render()、onClick())。 - 多态支持:客户端代码只需依赖接口,无需关心具体实现类。
2. 技术优势
- 解耦:产品实现与使用方完全隔离。
- 灵活性:一个类可以实现多个产品接口(如同时实现
Button和Draggable)。 - 语言特性(以Java/TypeScript为例):
interface Button { render(): void; } // 具体产品可以额外实现其他接口 class MacButton implements Button, Hoverable { render() { /*...*/ } onHover() { /*...*/ } }
3. 典型场景
- 当产品需要跨不同类层次结构时(如
WindowsButton和MacButton可能继承自不同的父类,但都需实现Button接口)。
二、抽象工厂为什么用抽象类?
1. 核心目的
- 提供默认逻辑:抽象类可以包含工厂方法的通用实现(如共享的初始化代码)。
- 模板方法模式:定义创建对象的算法骨架,将部分步骤延迟到子类。
2. 技术优势
- 代码复用:抽象类可以封装公共逻辑(如日志、权限检查)。
- 控制子类行为:通过抽象方法强制子类实现特定逻辑。
abstract class Dialog { // 工厂方法(必须由子类实现) abstract createButton(): Button; // 公共逻辑 render() { const button = this.createButton(); button.render(); console.log("Dialog渲染完成"); } }
3. 典型场景
- 当多个具体工厂需要共享相同的基础逻辑时(如所有UI对话框都需要执行渲染后的日志记录)。
三、关键对比:接口 vs 抽象类
| 维度 | 接口(产品) | 抽象类(工厂) |
|---|---|---|
| 主要作用 | 定义行为契约 | 提供部分实现 + 定义抽象方法 |
| 多继承 | 支持(一个类可实现多个接口) | 不支持(单继承) |
| 状态 | 不能包含字段或具体方法 | 可包含字段和具体方法 |
| 设计意图 | "是什么"(能力描述) | "是什么+部分怎么做"(部分实现) |
四、实际应用中的变体
1. 抽象工厂也可以使用接口
当不需要共享默认逻辑时,纯接口更灵活:
interface DialogFactory {
createButton(): Button;
}
class MacDialog implements DialogFactory {
createButton() { return new MacButton(); }
}
2. 混合使用的情况
某些语言(如Java)允许抽象类实现接口,结合两者优势:
abstract class AbstractDialog implements DialogFactory {
// 既实现接口,又提供公共逻辑
public void show() {
Button btn = createButton();
btn.render();
}
}
五、为什么工厂方法模式通常这样设计?
1. 产品维度
- 变化点:具体产品的实现可能完全不同(如
WindowsButton和WebButton)。 - 解决方案:用接口描述最小契约,允许自由扩展。
2. 工厂维度
- 变化点:虽然创建逻辑不同,但可能共享流程(如渲染前后日志)。
- 解决方案:用抽象类封装公共流程,子类只关注差异部分。
六、违反原则的后果
1. 若产品用抽象类
- 问题:强制产品继承会限制灵活性(如无法让已有类成为产品)。
- 示例:
// ❌ 错误设计:产品用抽象类 abstract class AbstractButton { abstract render(): void; } // 现有类无法直接成为产品(可能已继承其他类) class ThirdPartyButton extends OtherLibComponent { /*...*/ }
2. 若工厂用接口
- 问题:重复实现相同逻辑(如每个工厂都要写渲染日志)。
- 示例:
// ❌ 冗余代码:工厂用接口 class MacDialog implements DialogFactory { createButton() { /*...*/ } render() { const button = this.createButton(); button.render(); console.log("Dialog渲染完成"); // 每个工厂都要重复 } }
七、现代语言的演进
- Java/Kotlin:接口现在支持默认方法(
default),模糊了与抽象类的界限。 - TypeScript:接口可扩展类,抽象类可实现接口,提供更多组合方式。
- Go:通过结构体组合实现类似效果(无传统继承)。
八、总结:设计时的决策链
- 产品设计:优先用接口,确保灵活性。
- 工厂设计:
- 需要共享代码 → 抽象类
- 需要多继承或纯契约 → 接口
- 语言特性:根据所用语言选择最合适的工具。
叁、抽象工厂
一、抽象工厂模式(Abstract Factory)🏭
1. 核心定义
- 作用:创建相关或依赖对象的家族,而无需指定具体类。
- 本质:工厂的工厂(生产多个系列的产品)。
- 关键词:产品族(一组配套使用的对象)。
2. 代码示例(跨平台UI组件)
假设需要创建一套跨平台(Windows/Mac)的UI组件(按钮 + 复选框):
// ------ 抽象产品 ------
interface Button {
render(): void;
}
interface Checkbox {
check(): void;
}
// ------ 具体产品 ------
class WindowsButton implements Button {
render() { console.log("Windows风格按钮"); }
}
class MacButton implements Button {
render() { console.log("Mac风格按钮"); }
}
class WindowsCheckbox implements Checkbox {
check() { console.log("Windows复选框"); }
}
class MacCheckbox implements Checkbox {
check() { console.log("Mac复选框"); }
}
// ------ 抽象工厂 ------
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// ------ 具体工厂 ------
class WindowsFactory implements GUIFactory {
createButton(): Button { return new WindowsButton(); }
createCheckbox(): Checkbox { return new WindowsCheckbox(); }
}
class MacFactory implements GUIFactory {
createButton(): Button { return new MacButton(); }
createCheckbox(): Checkbox { return new MacCheckbox(); }
}
// ------ 客户端使用 ------
function createUI(factory: GUIFactory) {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
button.render();
checkbox.check();
}
// 根据系统选择工厂
const os = "mac"; // 或 "windows"
const factory = os === "mac" ? new MacFactory() : new WindowsFactory();
createUI(factory);
3. UML类图 📐
classDiagram
class Button {
<<interface>>
+render()
}
class Checkbox {
<<interfac>>
+check()
}
class GUIFactory {
<<interface>>
+createButton()
+createCheckbox()
}
Button <|.. WindowsButton
Button <|.. MacButton
Checkbox <|.. WindowsCheckbox
Checkbox <|.. MacCheckbox
GUIFactory <|.. WindowsFactory
GUIFactory <|.. MacFactory
WindowsFactory --> WindowsButton
WindowsFactory --> WindowsCheckbox
MacFactory --> MacButton
MacFactory --> MacCheckbox
二、三种工厂模式对比 🔍
| 维度 | 简单工厂 🏗️ | 工厂方法 🏭 | 抽象工厂 🏢 |
|---|---|---|---|
| 核心目标 | 集中创建单一产品 | 延迟创建单一产品到子类 | 创建多个相关产品的家族 |
| 扩展性 | 修改工厂类(违反OCP) | 新增工厂子类(符合OCP) | 新增产品族工厂(符合OCP) |
| 复杂度 | 最简单 | 中等 | 最复杂 |
| 产品关系 | 无关的独立产品 | 单一产品多实现 | 多个配套产品(如UI组件套装) |
| 典型应用 | 全局配置对象 | 跨平台按钮 | 跨平台UI套件(按钮+复选框+菜单) |
三、模式间的演进关系 🔄
-
简单工厂 → 工厂方法
- 当产品需要动态扩展时,将创建逻辑拆解到子类。
- 举例:从硬编码的
PhoneFactory变为IPhoneFactory和AndroidPhoneFactory。
-
工厂方法 → 抽象工厂
- 当需要一组关联产品时,将多个工厂方法合并到一个抽象工厂。
- 举例:从单独的
ButtonFactory升级为同时生产按钮和复选框的GUIFactory。
四、生活化比喻 🌍
- 简单工厂:像便利店,直接告诉店员要什么商品(传入参数),店员帮你拿。
- 工厂方法:像品牌专卖店,不同品牌(子类)生产自己的产品(如Nike店卖Nike鞋)。
- 抽象工厂:像宜家家居,提供整套配套家具(沙发+茶几+衣柜风格一致)。
五、实际应用场景 🚀
1. 抽象工厂典型用例
- 跨平台UI库(Windows/Mac/Linux 组件)
- 游戏中的多风格场景(森林/沙漠主题的地形+植被+敌人)
- 数据库访问层(MySQL/Oracle 的连接器+命令+结果集)
2. 前端中的体现
- React 的
ReactNativeRenderer为 iOS/Android 提供不同的原生组件 - CSS 框架的 主题系统(如Ant Design的亮色/暗色模式)
六、如何选择模式? 🤔
- 只用简单工厂:当产品类型固定且不会扩展时(如全局配置)。
- 用工厂方法:当需要灵活扩展单一产品类型时(如插件系统)。
- 用抽象工厂:当需要保证多个关联产品的一致性时(如整套UI风格)。
七、一句话总结 🌟
- 简单工厂:
switch-case走天下 - 工厂方法:子类决定生产什么
- 抽象工厂:子类决定生产哪一套