抽象工厂模式:封装产品族创建逻辑,实现跨产品系列的解耦
大家好,我是冰点,今天我们继续聊设计模式,这次要讲的是抽象工厂模式。
前阵子做一个电商项目的“主题皮肤”功能时,我犯了个错——一开始把“按钮”“输入框”“弹窗”这些UI组件单独用工厂方法创建,结果要切换“暗黑模式”和“浅色模式”时,得分别改按钮工厂、输入框工厂的逻辑,代码改得手忙脚乱。后来才反应过来,这根本不是单个组件的问题,而是“一套风格相关的组件”需要一起切换,而这正是抽象工厂模式能搞定的核心场景。
为什么需要抽象工厂模式?先搞懂“产品族”这个关键
很多人会把抽象工厂和工厂方法搞混,其实核心区别就在于“产品族”这个概念。先明确两个定义,你就懂了:
产品等级结构:比如“按钮”就是一个产品等级,它下面有“暗黑按钮”“浅色按钮”等具体产品,对应工厂方法模式的“单个产品”。
产品族:指一组风格一致、属于不同产品等级的组件,比如“暗黑模式”产品族,就包含“暗黑按钮”“暗黑输入框”“暗黑弹窗”,这些组件要一起用才是完整的主题。
当你需要“统一管理一套相关的产品,并且要整体切换产品系列”时,工厂方法模式就不够用了,这时候就得靠抽象工厂模式。它能解决两个核心痛点:
避免产品族内组件不匹配。如果用工厂方法单独创建,很可能出现“暗黑按钮配浅色输入框”的混乱情况,抽象工厂能保证同一产品族的组件一起创建。
减少切换产品系列的成本。要从“暗黑模式”切到“浅色模式”,只需要换一个抽象工厂的实现,不用改每个组件的创建逻辑。
抽象工厂模式怎么实现?核心是“抽象工厂+产品族工厂”
还是用刚才的“主题皮肤”场景举例,带你一步步看实现逻辑。整个结构分为四层:抽象工厂、具体产品族工厂、抽象产品、具体产品。
第一步:定义所有产品的抽象接口(产品等级)
先给每个产品等级定规范,比如按钮、输入框、弹窗,各自要有“渲染”方法,确保不同风格的组件都符合统一接口。
// 抽象按钮(产品等级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();
}
}
实际用的时候,我踩过的两个关键坑
抽象工厂模式比工厂方法复杂,用的时候很容易走偏,分享两个我总结的经验:
别滥用抽象工厂,先判断是不是“产品族”。如果只是单个产品的创建(比如只创建按钮,不管输入框),用工厂方法就够了。只有当多个产品必须“配套使用”时,才考虑抽象工厂,不然会增加不必要的复杂度。
扩展产品族容易,扩展产品等级难。如果要加一个“护眼模式”产品族,只需要新增
EyeCareThemeFactory和对应的三个组件类,不用改旧代码;但如果要在产品族里加一个新的产品等级(比如加“下拉框”),就得修改所有工厂接口和实现类,这是抽象工厂的局限性,使用前要考虑清楚产品等级是否会变化。
最后对比:抽象工厂 vs 工厂方法
很多人分不清这两种模式,我用一张表总结核心区别,帮你快速判断该用哪个:
简单说,“单个产品找工厂方法,一套产品找抽象工厂”,记住这个口诀,就能快速选对模式。
抽象工厂模式场景判断清单
这份清单能帮你快速判断项目中是否需要用抽象工厂模式,避免“该用不用”或“滥用过度”的问题,核心围绕“产品族特征”和“业务需求”两大维度展开。
一、核心场景判断:先确认是否满足“产品族”前提
这是判断的基础,不满足以下任一条件,基本不需要用抽象工厂模式。
是否存在多个“配套使用”的产品等级?
例:主题皮肤(按钮+输入框+弹窗)、数据库访问(连接+语句+结果集),这些产品必须一起用才完整,而非单个独立产品。
反例:仅创建“支付方式”(单个产品等级),用工厂方法模式即可。
产品族内的组件是否有统一的风格/逻辑关联?
例:暗黑主题的所有组件都是“深色背景+浅色文字”,MySQL的所有数据库组件都遵循MySQL协议,组件间不能随意混搭。
反例:不同风格的按钮(暗黑按钮+浅色输入框)可单独组合,无强制关联,无需抽象工厂。
二、业务需求判断:再看是否需要“整体控制”
满足产品族前提后,结合业务需求进一步判断,以下场景优先考虑抽象工厂模式。
是否需要整体切换产品系列?
例:用户一键切换“暗黑/浅色主题”、系统切换“MySQL/Oracle数据库”,切换时需同时替换所有配套组件,而非逐个修改。
关键:切换成本高(逐个改组件不现实),需统一入口控制。
是否需要严格保证产品族内组件一致性?
例:电商订单系统中,“PC端订单”需配套“PC支付+PC物流”,“移动端订单”需配套“移动端支付+移动端物流”,不能出现“PC订单用移动端支付”的混乱情况。
关键:避免因组件错配导致的业务逻辑错误。
是否需要隔离产品创建逻辑与业务逻辑?
例:业务代码只需要“渲染UI”“执行数据库查询”,不需要知道“暗黑按钮怎么创建”“MySQL连接怎么初始化”,创建逻辑全部交给工厂。
关键:业务开发人员无需关注组件细节,只需调用工厂接口。
三、排除场景:这些情况别用抽象工厂模式
避免为了“用设计模式而用设计模式”,以下场景用抽象工厂会增加不必要的复杂度。
仅需创建单个产品等级,无配套组件。
例:只创建“日志工具”“缓存对象”,无其他关联产品,用简单工厂或工厂方法即可。
产品族几乎不会扩展,且产品等级可能频繁新增。
例:主题皮肤目前只有“暗黑/浅色”两种,且未来可能频繁加“下拉框”“导航栏”等新组件(扩展产品等级),此时改抽象工厂接口和所有实现类成本极高。
业务逻辑简单,直接new对象即可满足需求。
例:小型工具类项目,组件创建无复杂逻辑,且无需切换系列,硬用抽象工厂会让代码变冗余。
接下来我们还会继续拆解其他设计模式,如果你对抽象工厂模式有疑问,比如“怎么结合配置文件动态选择工厂”,或者想看看其他场景(比如不同数据库的驱动创建)的实现,都可以告诉我。
评论区