目 录CONTENT

文章目录

抽象工厂模式:封装产品族创建逻辑

冰点
2025-12-04 / 0 评论 / 0 点赞 / 5 阅读 / 26417 字

抽象工厂模式:封装产品族创建逻辑,实现跨产品系列的解耦

大家好,我是冰点,今天我们继续聊设计模式,这次要讲的是抽象工厂模式。

前阵子做一个电商项目的“主题皮肤”功能时,我犯了个错——一开始把“按钮”“输入框”“弹窗”这些UI组件单独用工厂方法创建,结果要切换“暗黑模式”和“浅色模式”时,得分别改按钮工厂、输入框工厂的逻辑,代码改得手忙脚乱。后来才反应过来,这根本不是单个组件的问题,而是“一套风格相关的组件”需要一起切换,而这正是抽象工厂模式能搞定的核心场景。

为什么需要抽象工厂模式?先搞懂“产品族”这个关键

很多人会把抽象工厂和工厂方法搞混,其实核心区别就在于“产品族”这个概念。先明确两个定义,你就懂了:

  • 产品等级结构:比如“按钮”就是一个产品等级,它下面有“暗黑按钮”“浅色按钮”等具体产品,对应工厂方法模式的“单个产品”。

  • 产品族:指一组风格一致、属于不同产品等级的组件,比如“暗黑模式”产品族,就包含“暗黑按钮”“暗黑输入框”“暗黑弹窗”,这些组件要一起用才是完整的主题。

当你需要“统一管理一套相关的产品,并且要整体切换产品系列”时,工厂方法模式就不够用了,这时候就得靠抽象工厂模式。它能解决两个核心痛点:

  1. 避免产品族内组件不匹配。如果用工厂方法单独创建,很可能出现“暗黑按钮配浅色输入框”的混乱情况,抽象工厂能保证同一产品族的组件一起创建。

  2. 减少切换产品系列的成本。要从“暗黑模式”切到“浅色模式”,只需要换一个抽象工厂的实现,不用改每个组件的创建逻辑。

抽象工厂模式怎么实现?核心是“抽象工厂+产品族工厂”

还是用刚才的“主题皮肤”场景举例,带你一步步看实现逻辑。整个结构分为四层:抽象工厂、具体产品族工厂、抽象产品、具体产品。

第一步:定义所有产品的抽象接口(产品等级)

先给每个产品等级定规范,比如按钮、输入框、弹窗,各自要有“渲染”方法,确保不同风格的组件都符合统一接口。

// 抽象按钮(产品等级1)
public interface Button {
    void render(); // 渲染按钮
}

// 抽象输入框(产品等级2)
public interface Input {
    void render(); // 渲染输入框
}

// 抽象弹窗(产品等级3)
public interface Dialog {
    void render(); // 渲染弹窗
}

第二步:实现具体产品(产品族内的组件)

针对“暗黑模式”和“浅色模式”两个产品族,分别实现上面的抽象接口,每个产品都属于特定的产品族。

// 暗黑模式产品族
public class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染暗黑风格按钮(黑色背景+白色文字)");
    }
}

public class DarkInput implements Input {
    @Override
    public void render() {
        System.out.println("渲染暗黑风格输入框(灰色背景+白色文字)");
    }
}

public class DarkDialog implements Dialog {
    @Override
    public void render() {
        System.out.println("渲染暗黑风格弹窗(深灰背景+白色标题)");
    }
}

// 浅色模式产品族
public class LightButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染浅色风格按钮(白色背景+黑色文字)");
    }
}

public class LightInput implements Input {
    @Override
    public void render() {
        System.out.println("渲染浅色风格输入框(白色背景+黑色文字)");
    }
}

public class LightDialog implements Dialog {
    @Override
    public void render() {
        System.out.println("渲染浅色风格弹窗(白色背景+黑色标题)");
    }
}

第三步:定义抽象工厂接口(统一产品族创建规范)

抽象工厂要包含创建“同一产品族所有产品”的方法,比如主题工厂必须能创建按钮、输入框、弹窗,这样每个具体工厂都得实现这三个方法,保证产品族的完整性。

// 抽象主题工厂(定义产品族的创建规范)
public interface ThemeFactory {
    Button createButton();   // 创建按钮
    Input createInput();     // 创建输入框
    Dialog createDialog();   // 创建弹窗
}

第四步:实现具体产品族工厂

给每个产品族配一个专属工厂,比如暗黑主题工厂只创建暗黑系列的组件,浅色主题工厂只创建浅色系列的组件,确保组件风格统一。

// 暗黑主题工厂(产品族1)
public class DarkThemeFactory implements ThemeFactory {
    @Override
    public Button createButton() {
        return new DarkButton();
    }

    @Override
    public Input createInput() {
        return new DarkInput();
    }

    @Override
    public Dialog createDialog() {
        return new DarkDialog();
    }
}

// 浅色主题工厂(产品族2)
public class LightThemeFactory implements ThemeFactory {
    @Override
    public Button createButton() {
        return new LightButton();
    }

    @Override
    public Input createInput() {
        return new LightInput();
    }

    @Override
    public Dialog createDialog() {
        return new LightDialog();
    }
}

第五步:在业务中使用抽象工厂

业务代码里只依赖抽象工厂和抽象产品,不用关心具体是哪个产品族。要切换主题时,只需要换一个工厂实例,其他代码完全不用动。

public class UIService {
    private ThemeFactory themeFactory;

    // 通过构造方法注入工厂,灵活切换产品族
    public UIService(ThemeFactory themeFactory) {
        this.themeFactory = themeFactory;
    }

    // 渲染整个页面的UI组件
    public void renderUI() {
        Button button = themeFactory.createButton();
        Input input = themeFactory.createInput();
        Dialog dialog = themeFactory.createDialog();

        button.render();
        input.render();
        dialog.render();
    }

    // 测试:切换不同主题
    public static void main(String[] args) {
        // 用暗黑主题
        UIService darkUIService = new UIService(new DarkThemeFactory());
        darkUIService.renderUI();

        // 切换到浅色主题,只需换工厂
        UIService lightUIService = new UIService(new LightThemeFactory());
        lightUIService.renderUI();
    }
}

实际用的时候,我踩过的两个关键坑

抽象工厂模式比工厂方法复杂,用的时候很容易走偏,分享两个我总结的经验:

  1. 别滥用抽象工厂,先判断是不是“产品族”。如果只是单个产品的创建(比如只创建按钮,不管输入框),用工厂方法就够了。只有当多个产品必须“配套使用”时,才考虑抽象工厂,不然会增加不必要的复杂度。

  2. 扩展产品族容易,扩展产品等级难。如果要加一个“护眼模式”产品族,只需要新增EyeCareThemeFactory和对应的三个组件类,不用改旧代码;但如果要在产品族里加一个新的产品等级(比如加“下拉框”),就得修改所有工厂接口和实现类,这是抽象工厂的局限性,使用前要考虑清楚产品等级是否会变化。

最后对比:抽象工厂 vs 工厂方法

很多人分不清这两种模式,我用一张表总结核心区别,帮你快速判断该用哪个:

对比维度

工厂方法模式

抽象工厂模式

管理对象

单个产品等级(如“按钮”)

多个相关的产品族(如“暗黑主题全套组件”)

工厂核心方法

1个创建方法(如createButton)

N个创建方法(对应N个产品等级)

适用场景

单个产品的扩展(如加不同按钮)

产品族的整体切换(如换主题)

扩展难度

扩展产品易,扩展产品等级易

扩展产品族易,扩展产品等级难

简单说,“单个产品找工厂方法,一套产品找抽象工厂”,记住这个口诀,就能快速选对模式。

抽象工厂模式场景判断清单

这份清单能帮你快速判断项目中是否需要用抽象工厂模式,避免“该用不用”或“滥用过度”的问题,核心围绕“产品族特征”和“业务需求”两大维度展开。

一、核心场景判断:先确认是否满足“产品族”前提

这是判断的基础,不满足以下任一条件,基本不需要用抽象工厂模式。

  1. 是否存在多个“配套使用”的产品等级?

    • 例:主题皮肤(按钮+输入框+弹窗)、数据库访问(连接+语句+结果集),这些产品必须一起用才完整,而非单个独立产品。

    • 反例:仅创建“支付方式”(单个产品等级),用工厂方法模式即可。

  2. 产品族内的组件是否有统一的风格/逻辑关联?

    • 例:暗黑主题的所有组件都是“深色背景+浅色文字”,MySQL的所有数据库组件都遵循MySQL协议,组件间不能随意混搭。

    • 反例:不同风格的按钮(暗黑按钮+浅色输入框)可单独组合,无强制关联,无需抽象工厂。

二、业务需求判断:再看是否需要“整体控制”

满足产品族前提后,结合业务需求进一步判断,以下场景优先考虑抽象工厂模式。

  1. 是否需要整体切换产品系列?

    • 例:用户一键切换“暗黑/浅色主题”、系统切换“MySQL/Oracle数据库”,切换时需同时替换所有配套组件,而非逐个修改。

    • 关键:切换成本高(逐个改组件不现实),需统一入口控制。

  2. 是否需要严格保证产品族内组件一致性?

    • 例:电商订单系统中,“PC端订单”需配套“PC支付+PC物流”,“移动端订单”需配套“移动端支付+移动端物流”,不能出现“PC订单用移动端支付”的混乱情况。

    • 关键:避免因组件错配导致的业务逻辑错误。

  3. 是否需要隔离产品创建逻辑与业务逻辑?

    • 例:业务代码只需要“渲染UI”“执行数据库查询”,不需要知道“暗黑按钮怎么创建”“MySQL连接怎么初始化”,创建逻辑全部交给工厂。

    • 关键:业务开发人员无需关注组件细节,只需调用工厂接口。

三、排除场景:这些情况别用抽象工厂模式

避免为了“用设计模式而用设计模式”,以下场景用抽象工厂会增加不必要的复杂度。

  1. 仅需创建单个产品等级,无配套组件。

    • 例:只创建“日志工具”“缓存对象”,无其他关联产品,用简单工厂或工厂方法即可。

  2. 产品族几乎不会扩展,且产品等级可能频繁新增。

    • 例:主题皮肤目前只有“暗黑/浅色”两种,且未来可能频繁加“下拉框”“导航栏”等新组件(扩展产品等级),此时改抽象工厂接口和所有实现类成本极高。

  3. 业务逻辑简单,直接new对象即可满足需求。

    • 例:小型工具类项目,组件创建无复杂逻辑,且无需切换系列,硬用抽象工厂会让代码变冗余。

接下来我们还会继续拆解其他设计模式,如果你对抽象工厂模式有疑问,比如“怎么结合配置文件动态选择工厂”,或者想看看其他场景(比如不同数据库的驱动创建)的实现,都可以告诉我。

0

评论区