1. 从实际案例看两种工厂模式的应用场景
去年在重构一个电商平台的商品推荐系统时,我遇到了一个典型的设计难题:系统需要根据用户所在地区(国内/海外)和会员等级(普通/VIP)动态创建不同的推荐策略组合。最初我尝试用简单工厂实现,结果代码很快变成了充斥着if-else的"面条代码"。这时工厂方法模式和抽象工厂模式进入了我的视线,但究竟该选哪个?这个问题困扰了我整整两天。
1.1 工厂方法解决单一维度变化
先来看工厂方法模式。当系统只需要处理单一维度的变化时(比如仅按地区区分),工厂方法是最直观的选择。我们为每个具体产品(国内推荐策略、海外推荐策略)创建对应的工厂类。在Java中的典型实现是这样的:
java复制// 产品接口
interface RecommendationStrategy {
void generateRecommendations();
}
// 具体产品
class DomesticStrategy implements RecommendationStrategy {
@Override
public void generateRecommendations() {
System.out.println("生成国内推荐逻辑...");
}
}
// 工厂接口
interface StrategyFactory {
RecommendationStrategy createStrategy();
}
// 具体工厂
class DomesticStrategyFactory implements StrategyFactory {
@Override
public RecommendationStrategy createStrategy() {
return new DomesticStrategy();
}
}
这种方式的优势在于符合开闭原则——当需要新增一个地区策略时,只需添加新的策略类和对应的工厂类,无需修改现有代码。但当我尝试处理会员等级这个第二维度时,问题出现了。
1.2 抽象工厂应对多维度组合
抽象工厂模式专门解决这种"产品族"的创建问题。在我的案例中,产品族包含四个具体产品:国内普通策略、国内VIP策略、海外普通策略和海外VIP策略。UML类图呈现明显的"家族树"结构:
code复制AbstractFactory (StrategyFactory)
│
├── DomesticFactory
│ ├── createNormalStrategy() → DomesticNormalStrategy
│ └── createVIPStrategy() → DomesticVIPStrategy
│
└── OverseasFactory
├── createNormalStrategy() → OverseasNormalStrategy
└── createVIPStrategy() → OverseasVIPStrategy
对应的代码实现:
java复制// 抽象工厂
interface StrategyFactory {
NormalStrategy createNormalStrategy();
VIPStrategy createVIPStrategy();
}
// 具体工厂
class DomesticFactory implements StrategyFactory {
@Override
public NormalStrategy createNormalStrategy() {
return new DomesticNormalStrategy();
}
@Override
public VIPStrategy createVIPStrategy() {
return new DomesticVIPStrategy();
}
}
关键区别在于:工厂方法模式每个工厂只生产一种产品,而抽象工厂的每个工厂能生产一个产品族(多个相关联产品)。这就像手机制造——富士康某个车间只生产屏幕(工厂方法),而整个富士康园区能产出完整手机(抽象工厂)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从接口定义看本质差异
2.1 工厂方法的接口设计
工厂方法的核心是一个单一的创建方法。在标准实现中,Creator类声明了抽象的factoryMethod(),由具体子类实现。这种设计具有强烈的继承特性:
java复制abstract class StrategyCreator {
// 核心工厂方法
abstract RecommendationStrategy factoryMethod();
public void someOperation() {
RecommendationStrategy strategy = factoryMethod();
strategy.generateRecommendations();
}
}
class DomesticCreator extends StrategyCreator {
@Override
RecommendationStrategy factoryMethod() {
return new DomesticStrategy();
}
}
这种模式在框架设计中特别常见。比如Spring的BeanFactory就是典型的工厂方法应用,每个getBean()调用背后都是特定bean的创建逻辑。
2.2 抽象工厂的接口特征
抽象工厂接口则包含多个创建方法,每个方法对应产品族中的一个产品类型。这种设计更倾向于对象组合而非继承:
java复制interface GUIFactory {
Button createButton();
Menu createMenu();
Dialog createDialog();
}
class WinFactory implements GUIFactory {
@Override public Button createButton() { return new WinButton(); }
@Override public Menu createMenu() { return new WinMenu(); }
//...
}
在实际应用中,抽象工厂经常与单例模式结合使用。比如下面这个跨平台UI组件的实现:
java复制class UIFramework {
private static GUIFactory factory;
public static void init(String osType) {
if(osType.equals("Windows")) {
factory = new WinFactory();
} else {
factory = new MacFactory();
}
}
public static Button createButton() {
return factory.createButton();
}
}
关键经验:当发现自己在工厂方法中不断添加新的创建方法时,这往往预示着应该转向抽象工厂了。我在电商项目中就经历了这个过程——从最初只有createStrategy(),到后来不得不增加createVIPStrategy(),这时重构为抽象工厂就水到渠成了。
3. 复杂度与扩展性对比
3.1 类数量增长曲线
两种模式在应对变化时,类数量的增长方式截然不同。假设有P个产品等级(维度)和L个每个维度的选项:
- 工厂方法:需要P×L个具体工厂类 + P个产品接口
- 抽象工厂:需要L个具体工厂类 + P个产品接口
以我的电商案例来说(2个地区×2个会员等级):
- 工厂方法需要4个工厂类(DomesticNormalFactory, DomesticVIPFactory...)
- 抽象工厂只需2个工厂类(DomesticFactory, OverseasFactory)
但当新增一个维度时(比如增加设备类型:PC/移动端),情况就反转了:
- 工厂方法只需新增产品接口和对应工厂
- 抽象工厂需要修改所有现有工厂接口和实现
3.2 实际项目中的选择策略
根据我的经验,可以遵循以下决策流程:
-
当前和可预见的未来有多少个变化维度?
- 单一维度 → 工厂方法
- 多个关联维度 → 抽象工厂
-
产品之间是否存在强关联?
- 比如Windows按钮必须搭配Windows对话框 → 抽象工厂
- 可以独立使用 → 工厂方法
-
团队熟悉哪种模式?
- 新手团队建议先用工厂方法,抽象工厂的理解成本更高
在我的电商项目中,最终选择抽象工厂是因为:
- 地区和会员等级这两个维度高度耦合(VIP用户的海外推荐有特殊逻辑)
- 产品(策略)需要成套使用
- 团队有足够的设计模式经验
4. 典型应用场景剖析
4.1 工厂方法的经典案例
Spring框架的BeanFactory是工厂方法的完美示范。每个bean的获取本质上都是一个工厂方法调用:
java复制public interface BeanFactory {
Object getBean(String name) throws BeansException;
// 其他工厂方法变体...
}
// 具体实现
class DefaultListableBeanFactory implements BeanFactory {
@Override
public Object getBean(String name) {
// 复杂的bean创建逻辑
}
}
另一个典型案例是JDK中的Collection.iterator():
java复制public interface Collection<E> {
Iterator<E> iterator(); // 这就是工厂方法
}
// ArrayList中的实现
public Iterator<E> iterator() {
return new Itr();
}
4.2 抽象工厂的行业应用
跨平台UI框架是抽象工厂的"杀手级"应用场景。比如Java的AWT/Swing中:
java复制// 获取当前平台的工厂
GUIFactory factory = UIManager.getFactory();
// 创建成套的UI组件
Button btn = factory.createButton();
MenuBar menu = factory.createMenuBar();
在游戏开发中,抽象工厂用于创建风格一致的场景元素:
java复制interface GameAssetFactory {
Character createCharacter();
Building createBuilding();
Terrain createTerrain();
}
class MedievalFactory implements GameAssetFactory {
// 创建中世纪风格的所有游戏元素
}
class SciFiFactory implements GameAssetFactory {
// 创建科幻风格的所有游戏元素
}
我在实际开发中遇到过的一个坑:曾经尝试用抽象工厂管理数据库访问层,为不同数据库(MySQL/Oracle)创建Connection、Statement等对象。后来发现JBDC本身就是个抽象工厂(DriverManager.getConnection()),没必要自己再造轮子。这个教训告诉我:在使用设计模式前,先确认框架是否已经提供了相应实现。
5. 模式变体与实战技巧
5.1 带参数化的工厂方法
标准工厂方法每个子类只生产一种产品。在实践中,我经常使用带参数的变体:
java复制interface PaymentProcessorFactory {
PaymentProcessor createProcessor(PaymentType type);
}
class PayPalFactory implements PaymentProcessorFactory {
@Override
public PaymentProcessor createProcessor(PaymentType type) {
switch(type) {
case CREDIT_CARD: return new PayPalCreditCardProcessor();
case BANK_TRANSFER: return new PayPalBankProcessor();
//...
}
}
}
这种"混合模式"既有工厂方法的简洁性,又能处理一定程度的变体。但要注意:当参数组合超过3种时,就该考虑抽象工厂了。
5.2 抽象工厂的懒加载实现
抽象工厂的一个性能优化技巧是延迟创建产品对象。比如:
java复制class LazyDomesticFactory implements StrategyFactory {
private NormalStrategy normalStrategy;
private VIPStrategy vipStrategy;
@Override
public synchronized NormalStrategy createNormalStrategy() {
if(normalStrategy == null) {
normalStrategy = new DomesticNormalStrategy();
}
return normalStrategy;
}
//...
}
这在创建成本高的场景特别有用,比如数据库连接池或大型游戏资源。但要注意线程安全问题,我在一个高并发场景中就曾因为忘记加synchronized导致创建了重复对象。
5.3 单元测试中的模拟应用
两种工厂模式都能极大简化单元测试。通过注入不同的工厂,可以轻松切换真实对象和mock对象:
java复制// 测试代码
@Test
void testRecommendation() {
StrategyFactory testFactory = new TestStrategyFactory();
RecommendationService service = new RecommendationService(testFactory);
// 使用mock策略进行测试
service.generateRecommendations();
// 验证逻辑...
}
class TestStrategyFactory implements StrategyFactory {
@Override
public NormalStrategy createNormalStrategy() {
return new MockNormalStrategy();
}
//...
}
这个技巧在我参与的微服务项目中发挥了巨大作用,使测试代码量减少了约40%。
6. 反模式与常见误区
6.1 过度工程化的陷阱
我曾见过一个将简单需求复杂化的典型案例:一个只需要处理两种文件格式(CSV/JSON)的解析器,开发者却构建了包含12个类的抽象工厂体系。实际上,这里用简单的静态工厂方法就足够了:
java复制class ParserFactory {
public static DataParser createParser(String fileType) {
switch(fileType.toLowerCase()) {
case "csv": return new CsvParser();
case "json": return new JsonParser();
default: throw new IllegalArgumentException();
}
}
}
经验法则:当变化维度确实只有1-2个,且不会频繁扩展时,优先考虑简单实现。
6.2 混淆模式应用场景
常见的混淆情况包括:
- 在应该用抽象工厂的地方用了多个工厂方法,导致产品一致性难以保证
- 在应该用工厂方法的地方用了抽象工厂,造成不必要的复杂性
我总结的识别方法是:如果发现自己在写类似下面的代码,就该重新考虑模式选择了:
java复制// 错误示范:用工厂方法模拟抽象工厂
class StrategyCreator {
NormalStrategy createNormalStrategy();
VIPStrategy createVIPStrategy();
}
// 正确做法:明确使用抽象工厂
interface StrategyFactory {
NormalStrategy createNormalStrategy();
VIPStrategy createVIPStrategy();
}
6.3 忽视对象创建成本
无论是哪种工厂模式,都要注意:
- 避免在工厂方法中执行耗时操作(如数据库访问)
- 考虑使用对象池或缓存机制
- 对于轻量级对象,有时直接new比工厂更合适
在我的性能调优经历中,曾通过将频繁创建的轻量级对象从工厂模式改回直接实例化,使QPS提升了15%。这提醒我们:设计模式是手段,而非目的。
