1. 工厂模式:从基础到实战的深度解析
工厂模式作为Java设计模式中最常用的创建型模式,其核心价值在于解耦对象的创建与使用。想象一下你去餐厅点餐的场景——你只需要告诉服务员"我要一份牛排",而不需要关心牛排是哪个厨师做的、用什么火候煎的。工厂模式正是扮演了这个"服务员"的角色,让调用者专注于业务逻辑,而非对象创建的细节。
在实际开发中,我们通常会遇到两种主要的工厂模式实现:简单工厂(Simple Factory)和工厂方法(Factory Method)。这两种模式看似相似,但在设计理念和使用场景上有着本质区别。理解这些差异,对于写出既优雅又实用的代码至关重要。
2. 简单工厂模式:快速上手的实用方案
2.1 简单工厂的核心结构
简单工厂模式由一个工厂类和多个产品类组成。工厂类包含一个静态方法,根据输入参数决定创建哪种产品实例。这种模式最大的特点是简单直接,特别适合产品类型相对固定的场景。
让我们通过一个电商平台的支付系统案例来理解简单工厂。假设我们需要支持多种支付方式:
// 支付接口 interface Payment { void pay(double amount); } // 具体支付实现 class Alipay implements Payment { @Override public void pay(double amount) { System.out.println("使用支付宝支付:" + amount + "元"); } } class WechatPay implements Payment { @Override public void pay(double amount) { System.out.println("使用微信支付:" + amount + "元"); } } // 简单工厂 class PaymentFactory { public static Payment createPayment(String type) { switch (type.toLowerCase()) { case "alipay": return new Alipay(); case "wechat": return new WechatPay(); default: throw new IllegalArgumentException("不支持的支付类型"); } } }2.2 简单工厂的适用场景
简单工厂特别适合以下场景:
- 对象创建逻辑相对简单
- 产品类型数量有限且不太可能频繁扩展
- 需要快速实现解耦的场景
提示:在JDK中,
Calendar.getInstance()和NumberFormat.getInstance()都是简单工厂的典型应用。
2.3 简单工厂的局限性
虽然简单工厂易于理解和实现,但它有一个明显的缺点:违反开闭原则。当需要新增支付方式时,必须修改工厂类的代码。例如,要增加银联支付:
class UnionPay implements Payment { @Override public void pay(double amount) { System.out.println("使用银联支付:" + amount + "元"); } } // 必须修改工厂类 class PaymentFactory { public static Payment createPayment(String type) { switch (type.toLowerCase()) { // 原有case... case "union": return new UnionPay(); default: throw new IllegalArgumentException("不支持的支付类型"); } } }这种修改方式在大型系统中会导致工厂类变得越来越臃肿,维护成本也随之增加。
3. 工厂方法模式:面向扩展的设计
3.1 工厂方法的核心思想
工厂方法模式通过引入抽象工厂接口,将具体产品的创建延迟到子类中实现。这种设计完美遵循了开闭原则——对扩展开放,对修改关闭。
继续以支付系统为例,我们重构为工厂方法模式:
// 抽象工厂 interface PaymentFactory { Payment createPayment(); } // 具体工厂 class AlipayFactory implements PaymentFactory { @Override public Payment createPayment() { return new Alipay(); } } class WechatPayFactory implements PaymentFactory { @Override public Payment createPayment() { return new WechatPay(); } }3.2 工厂方法的优势
工厂方法模式的主要优势在于:
- 完全遵循开闭原则,新增支付方式只需添加新工厂类
- 客户端代码只依赖抽象工厂和抽象产品,耦合度更低
- 更适合框架设计,允许第三方扩展
例如,要新增银联支付,只需:
class UnionPayFactory implements PaymentFactory { @Override public Payment createPayment() { return new UnionPay(); } }原有代码完全不需要修改,真正实现了"对扩展开放,对修改关闭"。
3.3 工厂方法的代价
虽然工厂方法提供了极好的扩展性,但这种优势是有代价的:
- 类数量激增:每个产品都需要对应的工厂类
- 系统复杂度提高:需要理解更多的抽象层次
- 增加了代码阅读和维护的难度
4. 现代Java开发中的工厂模式实践
4.1 Spring依赖注入:工厂模式的终极形态
在现代Java企业开发中,Spring框架的依赖注入(DI)机制实际上实现了工厂模式的终极形态。Spring容器本质上是一个超级工厂,负责创建和管理所有bean对象。
通过注解方式使用DI:
@Service public class OrderService { private final PaymentService paymentService; @Autowired public OrderService(PaymentService paymentService) { this.paymentService = paymentService; } public void processOrder(Order order) { paymentService.processPayment(order.getAmount()); } }Spring会自动处理所有对象的创建和依赖关系,开发者完全不需要关心具体的实现类。
4.2 静态工厂方法的巧妙应用
即使在Spring生态中,静态工厂方法仍然有其用武之地。例如,创建复杂对象或需要特殊初始化的场景:
public class ComplexObjectFactory { public static ComplexObject createComplexObject(String config) { ComplexObject obj = new ComplexObject(); // 复杂的初始化逻辑 obj.init(config); return obj; } } // 在Spring配置中使用 @Bean public ComplexObject complexObject() { return ComplexObjectFactory.createComplexObject("default"); }4.3 工厂模式与策略模式的组合
在实际业务中,工厂模式常与策略模式配合使用,实现高度灵活的业务逻辑。例如,电商平台的不同促销策略:
// 策略接口 interface DiscountStrategy { double applyDiscount(double originalPrice); } // 具体策略 class SeasonalDiscount implements DiscountStrategy { @Override public double applyDiscount(double originalPrice) { return originalPrice * 0.8; } } // 策略工厂 class DiscountStrategyFactory { public static DiscountStrategy getStrategy(String type) { switch (type) { case "seasonal": return new SeasonalDiscount(); // 其他策略... default: return originalPrice -> originalPrice; } } }这种组合可以大幅减少业务代码中的条件判断,使系统更加灵活和可维护。
5. 实战中的避坑指南
5.1 避免过度设计
工厂方法虽然优雅,但并非所有场景都需要。评估标准包括:
- 产品类型是否会频繁扩展
- 系统规模和维护周期
- 团队的技术水平
对于小型项目或固定业务,简单工厂可能更实用。
5.2 正确处理异常情况
工厂模式中需要特别注意错误处理:
public Payment createPayment(String type) { Objects.requireNonNull(type, "支付类型不能为null"); try { // 创建逻辑 } catch (Exception e) { throw new PaymentCreationException("创建支付实例失败", e); } }5.3 性能考量
对于频繁创建的对象,可以考虑以下优化:
- 使用对象池
- 缓存常用实例
- 延迟初始化
class CachedPaymentFactory { private static final Map<String, Payment> cache = new ConcurrentHashMap<>(); public static Payment getPayment(String type) { return cache.computeIfAbsent(type, key -> { // 实际创建逻辑 }); } }6. 设计模式的选择策略
在实际项目中选择工厂模式实现时,可以参考以下决策树:
是否使用Spring框架?
- 是 → 优先使用依赖注入
- 否 → 进入下一步
产品类型是否固定?
- 是 → 简单工厂
- 否 → 工厂方法
是否需要支持第三方扩展?
- 是 → 工厂方法
- 否 → 简单工厂
记住一个实用口诀:"能用注入就用注入,不能注入写静态方法,除非你在开发框架才考虑工厂方法"。
7. 高级应用场景
7.1 泛型工厂
Java泛型可以与工厂模式结合,创建类型安全的工厂:
interface GenericFactory<T> { T create(); } class StringFactory implements GenericFactory<String> { @Override public String create() { return new String(); } }7.2 多态工厂
工厂本身也可以是多态的,实现更灵活的对象创建:
interface AbstractFactory { ProductA createProductA(); ProductB createProductB(); } class ConcreteFactory1 implements AbstractFactory { // 实现方法... }7.3 依赖查找 vs 依赖注入
理解这两种方式的区别很重要:
- 依赖查找:主动从工厂或容器获取对象
- 依赖注入:被动接收依赖对象
现代Java开发更推荐使用依赖注入,因为它能实现更高的解耦度。
8. 测试策略
针对工厂模式的测试需要注意:
- 工厂类测试:验证是否能正确创建各种产品
- 产品类测试:独立于工厂测试产品功能
- 集成测试:验证工厂和产品的协作
@Test void testPaymentFactory() { Payment alipay = PaymentFactory.createPayment("alipay"); assertTrue(alipay instanceof Alipay); Payment wechat = PaymentFactory.createPayment("wechat"); assertTrue(wechat instanceof WechatPay); }9. 性能优化技巧
- 使用枚举实现简单工厂:
enum PaymentType { ALIPAY { @Override public Payment create() { return new Alipay(); } }, WECHAT { @Override public Payment create() { return new WechatPay(); } }; public abstract Payment create(); }预初始化常用实例
考虑使用享元模式共享不变部分
10. 与其他模式的协作
工厂模式常与其他模式配合使用:
- 单例模式:工厂本身可能是单例
- 原型模式:通过复制原型创建新对象
- 建造者模式:创建复杂对象
理解这些模式的协作关系,可以设计出更优雅的解决方案。