1. 简单工厂模式概述
简单工厂模式(Simple Factory Pattern)是设计模式中最基础的一种创建型模式。它通过一个工厂类来封装对象的创建过程,客户端无需知道具体类的名称,只需要知道工厂类即可获取所需对象。这种模式在需要根据条件动态创建不同类实例的场景中尤为实用。
我在实际项目中最常遇到的应用场景是支付系统。当系统需要支持微信支付、支付宝支付、银联支付等多种支付方式时,简单工厂模式可以优雅地解决支付对象创建的问题。客户端只需要传递"wechat"、"alipay"等参数,工厂就能返回对应的支付处理器实例。
2. 模式结构与核心组件
2.1 UML类图解析
简单工厂模式包含三个核心角色:
- 工厂类(Factory):负责创建具体产品对象的中心枢纽,包含创建对象的静态方法
- 抽象产品(Product):定义产品对象的公共接口
- 具体产品(ConcreteProduct):实现抽象产品接口的具体类
classDiagram class Factory { +createProduct(type:String): Product } class Product { <<interface>> +use(): void } class ConcreteProductA { +use(): void } class ConcreteProductB { +use(): void } Factory --> Product Product <|-- ConcreteProductA Product <|-- ConcreteProductB2.2 代码实现示例
以支付系统为例,我们来看一个典型实现:
// 抽象产品 public interface Payment { void pay(BigDecimal amount); } // 具体产品 public class WechatPay implements Payment { @Override public void pay(BigDecimal amount) { System.out.println("使用微信支付:" + amount); } } public class Alipay implements Payment { @Override public void pay(BigDecimal amount) { System.out.println("使用支付宝支付:" + amount); } } // 工厂类 public class PaymentFactory { public static Payment createPayment(String type) { switch (type.toLowerCase()) { case "wechat": return new WechatPay(); case "alipay": return new Alipay(); default: throw new IllegalArgumentException("不支持的支付类型"); } } } // 客户端调用 public class Client { public static void main(String[] args) { Payment payment = PaymentFactory.createPayment("wechat"); payment.pay(new BigDecimal("100.00")); } }3. 模式优势与适用场景
3.1 核心优势分析
- 解耦客户端与具体实现:客户端只需要知道工厂类和抽象产品接口,无需关心具体实现类
- 集中化管理对象创建:所有创建逻辑集中在工厂类中,便于统一管理和修改
- 降低系统复杂度:客户端代码变得更简洁,不需要包含大量条件判断逻辑
3.2 典型应用场景
- 支付系统:如前面示例所示的各种支付方式
- 日志系统:创建文件日志、数据库日志等不同日志处理器
- 数据库连接:创建MySQL、Oracle等不同数据库连接
- UI组件库:根据不同平台创建对应的UI控件
- 游戏开发:根据角色类型创建不同的角色对象
提示:当系统中需要创建的对象类型较少且相对固定时,简单工厂模式是最佳选择。如果类型经常变化或数量庞大,考虑使用工厂方法模式。
4. 实现细节与最佳实践
4.1 工厂方法的三种实现方式
- 静态工厂方法(最常见)
public static Product createProduct(String type) { // 创建逻辑 }- 多方法工厂(避免类型字符串错误)
public static Product createProductA() { return new ConcreteProductA(); } public static Product createProductB() { return new ConcreteProductB(); }- 参数化工厂(使用枚举更安全)
public enum ProductType { TYPE_A, TYPE_B } public static Product createProduct(ProductType type) { switch (type) { case TYPE_A: return new ProductA(); case TYPE_B: return new ProductB(); } }4.2 设计注意事项
开闭原则的权衡:新增产品类型需要修改工厂类,这违反了开闭原则。这是简单工厂模式的主要局限。
工厂类的单例化:通常工厂类不需要维护状态,可以设计为单例或直接使用静态方法。
异常处理:当传入无效参数时,应该抛出明确的异常而非返回null。
命名规范:工厂方法名称通常使用"create"前缀,如createInstance、createProduct等。
5. 模式变体与扩展
5.1 带缓存的简单工厂
对于创建成本较高的对象,可以在工厂中引入缓存机制:
public class CachedFactory { private static final Map<String, Product> cache = new HashMap<>(); public static Product createProduct(String type) { if (cache.containsKey(type)) { return cache.get(type); } Product product; switch (type) { case "A": product = new ProductA(); break; case "B": product = new ProductB(); break; default: throw new IllegalArgumentException(); } cache.put(type, product); return product; } }5.2 结合依赖注入
在现代框架中,简单工厂常与依赖注入结合使用:
@Configuration public class AppConfig { @Bean @Scope("prototype") public Payment wechatPay() { return new WechatPay(); } @Bean @Scope("prototype") public Payment alipay() { return new Alipay(); } } @Service public class PaymentService { private final PaymentFactory factory; @Autowired public PaymentService(PaymentFactory factory) { this.factory = factory; } public void processPayment(String type) { Payment payment = factory.createPayment(type); payment.pay(amount); } }6. 常见问题与解决方案
6.1 问题1:如何避免修改工厂类?
场景:每次新增产品类型都需要修改工厂类的switch语句。
解决方案:
- 使用反射机制动态加载类
- 配合配置文件(如properties、XML)定义类型映射
- 使用服务发现机制(如Java的ServiceLoader)
示例:基于反射的实现
public class ReflectionFactory { private static final String PACKAGE = "com.example.products."; public static Product createProduct(String type) { try { Class<?> clazz = Class.forName(PACKAGE + type); return (Product) clazz.newInstance(); } catch (Exception e) { throw new RuntimeException("创建产品失败", e); } } }6.2 问题2:如何处理产品初始化参数?
场景:不同产品需要不同的初始化参数。
解决方案:
- 使用Builder模式配合工厂
- 定义统一的初始化接口
- 使用参数对象封装所有可能参数
示例:参数对象方案
public class ProductParams { private String configFile; private int timeout; // 其他参数... // getters/setters } public interface Product { void init(ProductParams params); } public class ProductFactory { public static Product createProduct(String type, ProductParams params) { Product product; switch (type) { case "A": product = new ProductA(); break; case "B": product = new ProductB(); break; default: throw new IllegalArgumentException(); } product.init(params); return product; } }7. 性能优化与线程安全
7.1 对象池技术
对于创建成本高且可复用的对象,可以结合对象池:
public class ProductPool { private static final Map<String, BlockingQueue<Product>> pools = new ConcurrentHashMap<>(); public static Product getProduct(String type) { pools.putIfAbsent(type, new LinkedBlockingQueue<>(10)); BlockingQueue<Product> pool = pools.get(type); Product product = pool.poll(); if (product == null) { product = ProductFactory.createProduct(type); } return product; } public static void returnProduct(String type, Product product) { if (product != null) { pools.get(type).offer(product); } } }7.2 线程安全考量
- 无状态工厂:静态工厂方法通常是线程安全的
- 有状态工厂:需要同步机制保护共享状态
- 双重检查锁定:适用于延迟初始化的场景
示例:线程安全的延迟初始化
public class SingletonFactory { private static volatile Product instance; public static Product getInstance() { if (instance == null) { synchronized (SingletonFactory.class) { if (instance == null) { instance = createProduct(); } } } return instance; } private static Product createProduct() { // 实际创建逻辑 } }8. 测试策略与Mock应用
8.1 工厂类的单元测试
public class PaymentFactoryTest { @Test public void testCreateWechatPay() { Payment payment = PaymentFactory.createPayment("wechat"); assertTrue(payment instanceof WechatPay); } @Test(expected = IllegalArgumentException.class) public void testInvalidType() { PaymentFactory.createPayment("invalid"); } }8.2 使用Mock对象测试客户端
public class ClientTest { @Test public void testClientWithMock() { // 创建mock产品 Payment mockPayment = mock(Payment.class); // 创建mock工厂 PaymentFactory factory = mock(PaymentFactory.class); when(factory.createPayment(anyString())).thenReturn(mockPayment); // 测试客户端 Client client = new Client(factory); client.processPayment(); verify(mockPayment).pay(any(BigDecimal.class)); } }9. 与其他模式的比较与结合
9.1 与工厂方法模式对比
| 特性 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 创建逻辑集中度 | 完全集中在一个工厂类 | 分散在各个具体工厂类 |
| 扩展性 | 需要修改工厂类代码 | 可以新增工厂类无需修改现有代码 |
| 复杂度 | 简单直观 | 类数量增加,结构更复杂 |
| 适用场景 | 产品类型少且稳定 | 产品类型多且可能频繁变化 |
9.2 与抽象工厂模式结合
简单工厂可以作为抽象工厂的基础构建块:
public interface GUIFactory { Button createButton(); Menu createMenu(); } public class WindowsFactory implements GUIFactory { @Override public Button createButton() { return SimpleButtonFactory.createButton("windows"); } @Override public Menu createMenu() { return SimpleMenuFactory.createMenu("windows"); } }10. 实际项目经验分享
在电商平台的优惠券系统中,我们使用简单工厂模式实现了多种优惠券类型的创建:
public interface Coupon { BigDecimal applyDiscount(BigDecimal originalPrice); } public class PercentageCoupon implements Coupon { private final BigDecimal percentage; public PercentageCoupon(BigDecimal percentage) { this.percentage = percentage; } @Override public BigDecimal applyDiscount(BigDecimal originalPrice) { return originalPrice.multiply(percentage); } } public class FixedAmountCoupon implements Coupon { private final BigDecimal amount; public FixedAmountCoupon(BigDecimal amount) { this.amount = amount; } @Override public BigDecimal applyDiscount(BigDecimal originalPrice) { return originalPrice.subtract(amount); } } public class CouponFactory { public static Coupon createCoupon(String type, BigDecimal value) { switch (type.toLowerCase()) { case "percentage": return new PercentageCoupon(value); case "fixed": return new FixedAmountCoupon(value); default: throw new IllegalArgumentException("不支持的优惠券类型"); } } }踩坑经验:
- 最初没有对value参数进行有效性验证,导致出现负折扣问题
- 优惠券类型使用字符串常量,后来改用枚举更安全
- 没有考虑线程安全问题,在高并发场景下出现对象状态混乱
优化后的实现:
public enum CouponType { PERCENTAGE, FIXED_AMOUNT } public class SafeCouponFactory { private static final Map<CouponType, Function<BigDecimal, Coupon>> creators = Map.of( CouponType.PERCENTAGE, PercentageCoupon::new, CouponType.FIXED_AMOUNT, FixedAmountCoupon::new ); public static Coupon createCoupon(CouponType type, BigDecimal value) { if (value.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("折扣值必须大于0"); } Function<BigDecimal, Coupon> creator = creators.get(type); if (creator == null) { throw new IllegalArgumentException("不支持的优惠券类型"); } return creator.apply(value); } }