1. 为什么说抽象工厂模式是工厂的工厂先提个问题你写代码的时候有没有遇到过这样的场景——一个业务模块里因为要支持不同的数据库、不同的消息队列、不同的UI风格导致if-else层层嵌套每加一种新类型就要回头改一遍老代码我早年做企业级系统的时候被这种代码折磨得不轻。后来真正吃透了抽象工厂模式才明白以前那些看似能跑的代码问题不是出在功能上而是出在扩展的代价上。抽象工厂模式英文是Abstract Factory Pattern它是GoF二十三种设计模式里创建型模式的重要一员。它的核心思想可以一句话讲清楚提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。听起来有点绕换个说法——它就是工厂的工厂先用一个工厂接口约定了我要生产什么产品族再用具体工厂决定我实际生产哪个品牌的产品。这篇文章不是来给你念概念定义的我想从实际的业务痛点出发拆解抽象工厂模式到底解决了什么问题、它的结构长什么样、怎么落地、有哪些坑以及它和普通工厂方法模式的本质区别。不管你是刚接触设计模式的新手还是写了好几年业务代码想回头梳理一下这篇文章都值得你花二十分钟读完。先说个我自己的体会设计模式这个东西光看书上写的类图是没有感觉的你只有把它放进一个你真正做过的项目里才知道它好在哪里、坏在哪里。文末我会用一个跨平台UI控件库的完整案例把抽象工厂模式从UML到代码到测试整个走一遍。你跟着敲一遍绝对比死记硬背十个模式的类图有用。2. 从真实业务困境出发产品族一致性为什么难维护2.1 混乱的调用方代码我当初踩过的硬编码坑假设你正在开发一套后台管理系统运营说要支持两套风格一套是面向内部员工的极简蓝主题另一套是面向VIP客户的尊贵金主题。两套主题不仅颜色、字体不一样连交互控件都不一样——比如蓝色主题下日期选择器是单面板金色主题下日期选择器是双面板。如果你的代码是这样写的public class LoginPage { private Button loginButton; private TextField usernameInput; private DatePicker datePicker; public LoginPage(String theme) { if (blue.equals(theme)) { loginButton new BlueButton(); usernameInput new BlueTextField(); datePicker new SinglePanelDatePicker(); } else if (gold.equals(theme)) { loginButton new GoldButton(); usernameInput new GoldTextField(); datePicker new DoublePanelDatePicker(); } } }看起来很直白对吧问题出现在两个地方调用方Client被迫知道所有具体类的存在。你每写一个页面都要在页面里维护一遍主题判断逻辑这个主题类型的if-else会散布在系统中几十上百个地方。产品族的一致性完全没有保障。今天开发忘了在某个页面加双面板日期选择器明天又有人在另一个页面把蓝色按钮和金色按钮混着用了。UI验收的时候运营指着页面说这个按钮怎么是蓝的——这种事故我经历过不止一次。2.2 对象之间的相互依赖才是问题核心抽象工厂模式解决的不是创建单个对象的问题而是创建一套相互依赖的对象的问题。这句话值得你停下来多想三秒。相互依赖是什么意思比如在主题系统中按钮和输入框之间可能存在对齐规则日期选择器要跟文本框共用一套圆角风格。你不能把任意两个控件拼在一起就完事它们必须作为一个整体出现。如果你用普通工厂方法模式Factory Method只能保证一个工厂生产一种产品但无法保证多个工厂生产的多个产品之间能兼容。举个反例你就明白了。假如你有一个ButtonFactory生产按钮还有一个TextFieldFactory生产输入框。这两个工厂各自独立订单来了你要的蓝色按钮和金色输入框都能造出来但组合在一起丑得没法看。抽象工厂的意义恰恰是把蓝色主题下的所有控件放在同一个工厂里让它们天然配套。所以我在团队里经常说一句话你要判断自己该不该用抽象工厂先问自己我是不是有一组对象必须以套为单位出现如果我只需要单独生产一种产品用工厂方法如果我要的是一套配合好的产品这就是抽象工厂的领地。2.3 回到问题本质谁在变化谁在保持稳定设计模式的本质是封装变化。抽象工厂模式里变化的维度有两个产品族主题蓝主题、金主题未来可能加粉主题、夜主题——这是横向维度。产品等级控件种类按钮、输入框、日期选择器未来可能加下拉框、弹窗——这是纵向维度。抽象工厂模式的结构相当于在二维坐标系里建立一个网格每个具体工厂负责一个产品族一行每个工厂里的方法负责生产该族下的一种产品一列。当你增加一个产品族时只需要新增一行新工厂所有已有代码不需要改动当你增加一个产品等级时就需要动到所有工厂——因为每个工厂都得新增一个方法。这个特性决定了抽象工厂模式对扩展产品族友好对增加产品等级不友好。很多书会把这个说成抽象工厂模式的缺点但我不这样看。这根本不是缺点而是结构本身的取舍。任何模式都有适用边界你把新增产品等级的场景硬套进抽象工厂用错工具当然会觉得它别扭。3. 抽象工厂模式的四大角色拆解把类图翻译成人话3.1 角色的名字和职责抽象工厂模式有四个核心角色我先把它们列出来然后逐个用大白话解释。角色名称职责AbstractFactory抽象工厂定义生产产品族的接口通常每个工厂方法返回一种抽象产品ConcreteFactory具体工厂实现抽象工厂生产一组具体的产品族AbstractProduct抽象产品定义产品的公共接口比如Button接口ConcreteProduct具体产品实现抽象产品接口比如BlueButton、GoldButton画成类图就是一张很经典的图抽象工厂在顶部指向多个抽象产品具体工厂在左下角实现抽象工厂的同时连接到具体产品客户端持有抽象工厂和抽象产品不关心具体实现。3.2 一个能直接跑的最小示例主题控件工厂我不画UML了直接上代码这个例子你拿过去就能跑把它跑通之后再回去看书上的类图一眼就懂。先定义产品接口// 抽象产品按钮 public interface Button { void render(); void click(); } // 抽象产品输入框 public interface TextField { void render(); void setPlaceholder(String text); } // 抽象产品日期选择器 public interface DatePicker { void render(); void setRange(Date start, Date end); }然后实现蓝色主题的具体产品public class BlueButton implements Button { Override public void render() { System.out.println(渲染蓝色主题按钮); } Override public void click() { System.out.println(蓝色按钮点击反馈); } } public class BlueTextField implements TextField { Override public void render() { System.out.println(渲染蓝色主题输入框); } Override public void setPlaceholder(String text) { System.out.println(蓝色输入框占位符: text); } } public class BlueDatePicker implements DatePicker { Override public void render() { System.out.println(渲染蓝色主题单面板日期选择器); } Override public void setRange(Date start, Date end) { System.out.println(蓝色日期范围: start - end); } }金色主题的类似我就不重复贴了你按同样的方式写一个GoldButton、GoldTextField、GoldDatePicker。接下来是抽象工厂和具体工厂// 抽象工厂 public interface UIFactory { Button createButton(); TextField createTextField(); DatePicker createDatePicker(); } // 具体工厂蓝色主题工厂 public class BlueUIFactory implements UIFactory { Override public Button createButton() { return new BlueButton(); } Override public TextField createTextField() { return new BlueTextField(); } Override public DatePicker createDatePicker() { return new BlueDatePicker(); } } // 具体工厂金色主题工厂 public class GoldUIFactory implements UIFactory { Override public Button createButton() { return new GoldButton(); } Override public TextField createTextField() { return new GoldTextField(); } Override public DatePicker createDatePicker() { return new GoldDatePicker(); } }最后是客户端代码加入了简单的工厂选择逻辑public class Application { private Button button; private TextField textField; private DatePicker datePicker; public Application(UIFactory factory) { // 客户端只依赖抽象工厂不依赖任何具体类 this.button factory.createButton(); this.textField factory.createTextField(); this.datePicker factory.createDatePicker(); } public void render() { button.render(); textField.render(); datePicker.render(); } public static void main(String[] args) { // 运行时决定使用哪个工厂 UIFactory factory config(gold); Application app new Application(factory); app.render(); } private static UIFactory config(String theme) { if (blue.equals(theme)) { return new BlueUIFactory(); } else if (gold.equals(theme)) { return new GoldUIFactory(); } throw new IllegalArgumentException(未知主题: theme); } }注意看在Application类里button、textField、datePicker的声明类型都是抽象接口构造函数接收的也是UIFactory接口。这就是抽象工厂模式的核心价值客户端代码和具体产品完全解耦它甚至都不知道BlueButton这个类存在。全局只有一个config方法里出现了具体工厂的名字以后要加粉色主题只需要写一个PinkUIFactory然后在config里加一行分支全部搞定。3.3 客户端为什么可以做到完全不懂产品细节有人会问客户端只拿到了抽象产品接口那它调用button.render()的时候JVM怎么知道到底该执行BlueButton.render()还是GoldButton.render()这就涉及Java的多态机制了。工厂返回的虽然是接口类型但对象本质上是具体类的实例只是它的标签被贴成了接口。运行时调用方法时JVM会根据对象的实际类型进行动态绑定找到正确的实现类去执行。这是Java/C#这类面向对象语言里的常识但很多初学者会在这里绕不过来。记住一句话接口类型引用 多态方法分派 客户端无感知的具体实现替换。抽象工厂模式依赖这个机制它才能实现换主题只需换工厂其他代码一行不动的效果。4. 抽象工厂 vs 工厂方法一张表说清两者的边界4.1 核心区别不在方法数量而在产品关联性很多人会把抽象工厂模式和工厂方法模式搞混觉得工厂方法是一个方法创建一种产品抽象工厂是多个方法创建多种产品——这只是表面现象。真正的区别在于抽象工厂创建的一组产品之间有内在关联必须成套出现。工厂方法模式的重点在继承它让子类决定某个产品怎么创建抽象工厂模式的重点在组合它把多个产品的创建逻辑组合在一个工厂接口里保证产品族的一致性。举个电商业务的例子。假设你要生成订单通知消息通知方式有短信和邮件两种。如果你只需要生成一条通知消息用工厂方法就够一个MessageFactory子类SmsMessageFactory和EmailMessageFactory分别生成短信和邮件。但如果你想给VIP用户同时发短信邮件站内信三连通知并且要求这三者的文案、模板、语气都是一套风格那你就需要抽象工厂——NotificationFactory接口提供createSms()、createEmail()、createSiteMessage()三个方法VipNotificationFactory实现这三个方法并且保证风格统一。4.2 各自的使用场景对照对比维度工厂方法模式抽象工厂模式意图定义一个创建对象的接口让子类决定实例化哪个类提供一个创建一系列相关对象的接口产品数量一种产品一族产品多种产品产品之间的关系无关联相互独立强关联必须成套使用新增产品族新增一个子类新增一个具体工厂类新增产品等级修改抽象工厂接口修改抽象工厂接口典型场景日志记录器、数据库连接UI主题、跨平台控件、数据库访问层这里需要特别提醒抽象工厂模式不适合产品之间无关联的场景。如果你只是想让每次都能创建不同类型的对象用工厂方法就足够了硬上抽象工厂只会增加接口的复杂度让代码显得很臃肿。我在Code Review的时候见过很多人拿抽象工厂套一切最后接口里十几个方法实际用到的没几个这就是过度设计。4.3 换个角度理解抽象工厂是工厂方法的团队版如果你用过工厂方法模式再看抽象工厂可以把它理解为将多个工厂方法组织进同一个接口。抽象工厂里的每个方法本质上都是一个工厂方法模式的应用。区别只在于这些工厂方法必须绑定在同一个工厂里内部共享同一套配置和依赖。我曾经在一个项目里用工厂方法模式分别创建了RedisCache、MySQLRepository和KafkaProducer三个组件每个组件各用各的工厂。结果到了上线前运维说要拿一套仿真环境来测仿真环境里的Redis是mock的MySQL是内存库Kafka是假的。如果我用的是抽象工厂模式只需要做一个SimulationEnvironmentFactory把这三个mock组件都生产出来整个系统切到仿真环境就是一行配置的事。而我当时用的是三个独立的工厂方法结果改配置改了半个下午每个组件都单独调一遍。这个经历后来成了我在团队里讲产品族概念时的经典案例。当你发现你的系统里有多个组件必须一起切换时你有意识地用抽象工厂去收敛它们是一种非常聪明的架构决策。5. 深入实战多数据库支持场景下的抽象工厂落地5.1 场景设定一个需要同时支持MySQL和PostgreSQL的报表系统我做过的另一个实际项目是报表系统它要支持MySQL和PostgreSQL两种数据库订单数据、用户数据、报表数据分别访问。刚开始团队里的做法是在数据访问层写一堆if (dbType mysql)的分支每个查询方法里都判断一遍。到后来需求越来越多代码里到处是这种判断改配置都要提心吊胆生怕漏了一个地方。后来重构的时候我引入了抽象工厂模式效果立竿见影。下面是简化版的实现你感受一下整个结构。定义抽象产品接口代表不同类型的数据访问对象public interface OrderDAO { ListOrder queryOrders(); void insertOrder(Order order); } public interface UserDAO { User queryUserById(String userId); void updateUser(User user); }MySQL的实现public class MySQLOrderDAO implements OrderDAO { Override public ListOrder queryOrders() { // MySQL专用SQL实现 System.out.println(执行MySQL订单查询); return null; } Override public void insertOrder(Order order) { System.out.println(执行MySQL订单插入); } } public class MySQLUserDAO implements UserDAO { // MySQL用户DAO实现省略具体逻辑 }PostgreSQL的实现类似不重复贴。然后是抽象工厂接口public interface DAOFactory { OrderDAO createOrderDAO(); UserDAO createUserDAO(); }两个具体工厂public class MySQLDAOFactory implements DAOFactory { Override public OrderDAO createOrderDAO() { return new MySQLOrderDAO(); } Override public UserDAO createUserDAO() { return new MySQLUserDAO(); } } public class PostgreSQLDAOFactory implements DAOFactory { Override public OrderDAO createOrderDAO() { return new PostgreSQLOrderDAO(); } Override public UserDAO createUserDAO() { return new PostgreSQLUserDAO(); } }客户端代码public class ReportService { private final OrderDAO orderDAO; private final UserDAO userDAO; public ReportService(DAOFactory factory) { this.orderDAO factory.createOrderDAO(); this.userDAO factory.createUserDAO(); } public void generateReport() { ListOrder orders orderDAO.queryOrders(); // 逻辑处理 } }这个设计的好处一眼就能看出来以后要加Oracle支持新建一个OracleDAOFactory把OracleOrderDAO、OracleUserDAO写出来然后在装配层把工厂换成OracleDAOFactory整个系统就能跑。原有的ReportService代码一行都不用改测试也不用重写。5.2 工厂创建时机启动时装配 vs 每次使用时获取刚才代码里ReportService的构造函数要求传入一个DAOFactory。那么问题来了这个工厂是谁创建的在什么时机创建的两种常见做法做法一系统启动时装配推荐在应用启动的地方根据配置读取数据库类型创建一次性工厂然后通过依赖注入把工厂传给各个Service。DAOFactory factory createFactoryByConfig(); ReportService reportService new ReportService(factory);做法二每个Service自己判断不推荐在Service内部根据配置文件判断数据库类型自己创建工厂。这个做法会把工厂选择和Service逻辑耦合在一起违背了抽象工厂的初衷。我在项目中实测下来推荐做法的好处是切换数据库只需要改配置文件重启应用全部组件统一切换。因为工厂本身是无状态的你可以安全地把它做成单例不会产生并发问题。5.3 配置驱动把选择工厂这件事交给Spring在Spring项目中我们可以把工厂选择做成配置驱动非常干净。先在配置里声明三个BeanConfiguration public class DatabaseConfig { Value(${app.db.type}) private String dbType; Bean public DAOFactory daoFactory() { if (mysql.equals(dbType)) { return new MySQLDAOFactory(); } else if (postgresql.equals(dbType)) { return new PostgreSQLDAOFactory(); } throw new IllegalArgumentException(Unsupported db type: dbType); } }然后在Service里直接注入Service public class ReportService { private final OrderDAO orderDAO; private final UserDAO userDAO; public ReportService(DAOFactory factory) { this.orderDAO factory.createOrderDAO(); this.userDAO factory.createUserDAO(); } }切换数据库只需修改application.properties里的一行app.db.typepostgresql整个过程你不再关心MySQLOrderDAO和PostgreSQLOrderDAO到底在哪里也不用在代码里保留任何数据库类型的判断。这就是抽象工厂模式在真实工程中的落地形态。6. 抽象工厂模式的扩展性陷阱变的是工厂变不得的是接口6.1 产品族扩展容易产品等级扩展昂贵前面提到过抽象工厂模式对增加产品族友好——新增主题、新增数据库都只需要新增具体工厂类。但对增加产品等级非常不友好每增加一种产品接口你必须在抽象工厂接口和所有具体工厂里各加一个方法。假设你的UI系统要增加一个下拉框控件改动的清单是这样的新增ComboBox抽象产品接口在UIFactory接口新增createComboBox()方法在BlueUIFactory新增BlueComboBox实现在GoldUIFactory新增GoldComboBox实现。如果已经接入了十个主题工厂那就是一个接口十个实现类一个抽象产品接口的改动量。这样的工作量说大不大说小也不小。关键是每改动一次抽象工厂接口所有依赖它的代码都要编译任何没有实现新方法的工厂都会报错——这既是约束也是保护。6.2 典型应对方案默认实现兜底如果产品等级扩展概率比较高可以用默认实现来兜底。例如在接口里加一个default方法Java 8之后支持返回一个通用的默认控件这样旧工厂可以不用改动直接继承默认行为。public interface UIFactory { Button createButton(); TextField createTextField(); DatePicker createDatePicker(); // 默认实现兜底新工厂可以重写 default ComboBox createComboBox() { return new DefaultComboBox(); } }这个技巧在渐进式重构里非常有用。你不需要一次把所有工厂都改完可以先提供默认实现让系统能编译然后逐个工厂覆盖为真正的定制实现。我习惯把这种方法叫作平滑扩展它能让团队避免一次改动牵动全局的阵痛。6.3 什么时候不该用抽象工厂模式我得泼一盆冷水不是所有多产品场景都适合抽象工厂。以下情况请谨慎使用产品之间没有关联性。比如你只想要一个能创建随机形状的工厂圆是圆、方是方彼此不搭界用工厂方法或简单工厂就够了。产品等级经常变化。如果你的系统几乎每个月都要加一种新产品类型抽象工厂要求你修改所有工厂这个成本你可能受不了。产品族维度单一。如果整个系统只有一种产品族搞抽象工厂就是多此一举。何必为了一个永远只有一套实现的接口增加抽象层团队成员设计模式水平参差不齐。这个理由可能有点政治不正确但却是现实中的大问题。抽象工厂模式引入的抽象层级比较多如果团队里有人不理解它的设计意图很容易把它用歪最后代码比不用设计模式还要难维护。6.4 模式不是银弹但组合使用更强大抽象工厂模式经常和其他模式组合使用。比如它和单例模式结合可以保证每个具体工厂在整个应用中只有一个实例它和工厂方法模式结合可以在一套抽象产品族内部再做一层产品细节的创建隔离它和原型模式结合可以用克隆对象替代工厂创建减少构造开销。在实际代码中这些模式不会像教科书那样纯净地出现。你看到一段代码很可能既是抽象工厂又用了单例还夹杂了点策略模式的思想。这很正常设计模式本来就不是僵化的教条而是一套解决问题的工具箱。关键不在于你用了哪个模式的名字而在于你的代码是否真正做到了高内聚、低耦合。7. 测试策略与踩坑笔记抽象工厂模式不是银弹7.1 单测里怎么用抽象工厂替换真实实现抽象工厂模式的另一个巨大优势是测试友好。以前面报表系统为例它依赖DAO但你在单元测试里并不想连真数据库。这时候只需要手写一个MockDAOFactory配上内存数据库或Mock对象就能把ReportService的依赖全部替换掉。public class MockDAOFactory implements DAOFactory { Override public OrderDAO createOrderDAO() { return new MockOrderDAO(); } Override public UserDAO createUserDAO() { return new MockUserDAO(); } }测试代码public class ReportServiceTest { Test public void testGenerateReport() { ReportService service new ReportService(new MockDAOFactory()); // 断言相关的行为 } }你会发现测试代码不需要任何Mockito之类的工具也能写出很干净的单测。因为抽象工厂模式和依赖注入天生搭配你只需要在测试装配层把工厂换掉即可。7.2 我踩过的坑工厂方法返回类型用了具体类有一次重构代码某个同事把工厂方法的返回类型直接写成了具体类比如public BlueButton createButton();当时看着没毛病反正工厂就是蓝色主题的工厂。但后来要增加一个银色主题时这个工厂没法复用因为返回类型被焊死在BlueButton了。抽象工厂模式下所有工厂方法的返回类型必须是抽象产品接口这是红线。一旦你返回具体类型客户端代码就直接依赖于具体实现整个模式就崩塌了。我后来在团队Code Review里定了一条规矩凡是工厂返回类型一律不允许返回具体类谁写了具体类Review直接打回。这条规矩实施之后抽象工厂相关的代码质量明显提升。7.3 另一个坑多个工厂方法共用可变状态当建立BlueUIFactory时你可能会在字段里保存一个共享的配置对象。如果这个工厂同时被多个线程调用字段被并发修改就会出现线程安全问题。比如public class BlueUIFactory implements UIFactory { private String themeConfig; // 共享可变状态 ... }解决思路很简单工厂最好是无状态的。如果需要传递配置在工厂方法参数里显式传入而不是存在字段里。实在要持有状态就给工厂加锁或者使用ThreadLocal但后者会让代码复杂度上升不是首选。我在实战中坚持无状态工厂原则多个Service可以安全共享同一个工厂实例极大简化了并发设计。7.4 抽象工厂模式的调试技巧调试抽象工厂代码时有个很实用的技巧在工厂方法里打日志或者用调试器观察返回对象的实际类型。因为客户端看到的是抽象接口很容易忽略对象底层是哪个具体实现导致查问题的时候一头雾水。打印日志是一个快速定位的思路System.out.println(订单DAO实例: orderDAO.getClass().getSimpleName());输出MySQLOrderDAO你就能确认客户端确实拿到了正确工厂的产品。这个技巧对排查配置改了但没生效这种问题尤其好用。我曾经就因为工厂Bean创建时机不对导致配置已经切到PostgreSQL但客户端一直拿的是MySQL的DAO排查了好久最后靠打印对象类型才发现问题。8. 延伸场景从跨平台UI到中间件适配的抽象工厂8.1 跨平台UI库Windows、macOS、Linux三端一致体验除了主题系统和数据库系统抽象工厂模式在跨平台UI库中也非常常见。你看Java的AWT或者JavaFX内部的控件工厂设计它们都需要根据操作系统决定渲染行为。如果我写一个跨平台应用需要处理滚动条、按钮、菜单栏我可以定义public interface PlatformWidgetFactory { ScrollBar createScrollBar(); MenuBar createMenuBar(); Button createButton(); }Windows工厂public class WindowsWidgetFactory implements PlatformWidgetFactory { Override public ScrollBar createScrollBar() { return new WindowsScrollBar(); } Override public MenuBar createMenuBar() { return new WindowsMenuBar(); } Override public Button createButton() { return new WindowsButton(); } }macOS工厂和Linux工厂同理。客户端完全不需要知道当前运行在什么平台上只要在启动时根据操作系统选一个工厂剩下的代码全部面对抽象接口。这样写出来的跨平台程序渲染行为高度一致不会出现Windows上按钮圆角macOS上按钮直角的不协调。8.2 中间件适配Kafka、RabbitMQ、RocketMQ的消息产品族假设公司要做一个统一的消息推送平台底层可以接Kafka、RabbitMQ、RocketMQ三种消息队列但业务方不需要关心具体是哪一种。消息产品的抽象接口可以是public interface MessageProducer { void publish(String topic, String payload); } public interface MessageConsumer { void subscribe(String topic, ConsumerRecordHandler handler); }具体产品public class KafkaProducer implements MessageProducer { ... } public class RabbitMQProducer implements MessageProducer { ... }工厂接口public interface MiddlewareFactory { MessageProducer createProducer(); MessageConsumer createConsumer(); }有了这个抽象业务代码只需要public class NotificationService { private final MessageProducer producer; private final MessageConsumer consumer; public NotificationService(MiddlewareFactory factory) { this.producer factory.createProducer(); this.consumer factory.createConsumer(); } }将来要把底层从Kafka切到RocketMQ只需新建一个RocketMQFactory然后在配置装配层替换Bean。整个业务代码零改动。这就是抽象工厂在中间件适配层面的典型应用。8.3 抽象工厂模式的兄弟模式Builder模式的区别有人会把抽象工厂模式和Builder模式混为一谈因为二者都涉及复杂对象创建。但它们有本质区别抽象工厂关注产品族的创建多个产品接口统一在一个工厂下。Builder模式关注单个复杂对象的逐步构建比如一个拥有几十个可选配置的ReportConfig对象。如果你要创建的是一个有大量可选参数的复杂对象用Builder如果你要创建的是一族相互配合的对象用抽象工厂。两者并不冲突可以嵌套使用——抽象工厂里的某个具体产品内部可能用Builder来组装。9. 最后想说的抽象工厂模式的内功心法抽象工厂模式的代码量其实不多但它的设计思想值得反复咀嚼。我把整个核心总结成三句内功心法面向抽象编程绝不面向具体类编程。客户端只依赖UIFactory、Button、TextField永远不出现BlueButton的名字这就是核心。产品族成套出现工厂保证一致性。要让系统里替换一个套的对象比替换单个对象更彻底——换工厂全家换。扩展的开关永远在配置和装配层。你不需要在业务代码里塞满if判断只需要在应用启动时或依赖注入处基于配置文件选择一个工厂。如果你要深入学我建议你亲手做这三件事第一步把文章里的主题控件例子从零敲一遍运行起来观察输出变化第二步新增一个粉色主题的工厂看看你需要改多少代码——你会发现只需要加一个新类和一个分支其他代码不用动第三步再新增一种下拉框控件感受一下所有工厂都要新增方法的约束理解模式适用边界的意义。我第一次接触抽象工厂模式时真的看懂了类图但始终体会不到它的价值。直到有一次在一个多数据库项目里因为需求变更需要紧急支持第三种数据库缓存系统我写了一个新的工厂类然后在配置文件里改了一行整个系统就跑起来了。那一刻我一下子明白了什么叫为扩展而生。希望你也能在某个项目里感受到这种酣畅淋漓的替换体验。好的架构不是炫技而是让未来的人包括未来的自己改起代码来更轻松。