鸡汤来喽!简单工厂vs工厂vs抽象工厂

290 阅读9分钟

壹、简单工厂、工厂

一、简单工厂模式(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 实例)

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. 技术优势

  • 解耦:产品实现与使用方完全隔离。
  • 灵活性:一个类可以实现多个产品接口(如同时实现 ButtonDraggable)。
  • 语言特性(以Java/TypeScript为例):
    interface Button {
        render(): void;
    }
    
    // 具体产品可以额外实现其他接口
    class MacButton implements Button, Hoverable {
        render() { /*...*/ }
        onHover() { /*...*/ }
    }
    

3. 典型场景

  • 当产品需要跨不同类层次结构时(如 WindowsButtonMacButton 可能继承自不同的父类,但都需实现 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. 产品维度

  • 变化点:具体产品的实现可能完全不同(如 WindowsButtonWebButton)。
  • 解决方案:用接口描述最小契约,允许自由扩展。

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:通过结构体组合实现类似效果(无传统继承)。

八、总结:设计时的决策链

  1. 产品设计:优先用接口,确保灵活性。
  2. 工厂设计
    • 需要共享代码 → 抽象类
    • 需要多继承或纯契约 → 接口
  3. 语言特性:根据所用语言选择最合适的工具。

叁、抽象工厂


一、抽象工厂模式(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套件(按钮+复选框+菜单)

三、模式间的演进关系 🔄

  1. 简单工厂 → 工厂方法

    • 当产品需要动态扩展时,将创建逻辑拆解到子类。
    • 举例:从硬编码的 PhoneFactory 变为 IPhoneFactoryAndroidPhoneFactory
  2. 工厂方法 → 抽象工厂

    • 当需要一组关联产品时,将多个工厂方法合并到一个抽象工厂。
    • 举例:从单独的 ButtonFactory 升级为同时生产按钮和复选框的 GUIFactory

四、生活化比喻 🌍

  • 简单工厂:像便利店,直接告诉店员要什么商品(传入参数),店员帮你拿。
  • 工厂方法:像品牌专卖店,不同品牌(子类)生产自己的产品(如Nike店卖Nike鞋)。
  • 抽象工厂:像宜家家居,提供整套配套家具(沙发+茶几+衣柜风格一致)。

五、实际应用场景 🚀

1. 抽象工厂典型用例

  • 跨平台UI库(Windows/Mac/Linux 组件)
  • 游戏中的多风格场景(森林/沙漠主题的地形+植被+敌人)
  • 数据库访问层(MySQL/Oracle 的连接器+命令+结果集)

2. 前端中的体现

  • React 的 ReactNativeRenderer 为 iOS/Android 提供不同的原生组件
  • CSS 框架的 主题系统(如Ant Design的亮色/暗色模式)

六、如何选择模式? 🤔

  1. 只用简单工厂:当产品类型固定且不会扩展时(如全局配置)。
  2. 用工厂方法:当需要灵活扩展单一产品类型时(如插件系统)。
  3. 用抽象工厂:当需要保证多个关联产品的一致性时(如整套UI风格)。

七、一句话总结 🌟

  • 简单工厂switch-case 走天下
  • 工厂方法:子类决定生产什么
  • 抽象工厂:子类决定生产哪一套