1. 简单工厂模式的核心价值
简单工厂模式(Simple Factory Pattern)是面向对象编程中最基础的设计模式之一,它通过将对象的创建过程封装在一个专门的工厂类中,实现了创建逻辑与业务逻辑的解耦。这种模式特别适用于需要频繁创建相似对象的场景。
在实际开发中,我们经常会遇到这样的场景:根据不同的条件创建不同类型的对象。比如一个图形绘制程序需要根据用户输入创建圆形、矩形或三角形对象。最直观的做法可能是这样的:
java复制if (shapeType.equals("circle")) {
return new Circle();
} else if (shapeType.equals("rectangle")) {
return new Rectangle();
} else if (shapeType.equals("triangle")) {
return new Triangle();
}
这种写法的问题在于,创建逻辑散落在代码各处,当需要新增图形类型时,必须修改所有创建该对象的地方。简单工厂模式通过将对象创建集中管理,完美解决了这个问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 简单工厂模式的结构解析
2.1 模式参与者
简单工厂模式通常包含三个关键角色:
- 工厂类(Factory):负责创建具体产品对象的类,包含一个创建对象的方法,通常为静态方法
- 抽象产品(Product):定义产品对象的接口,可以是抽象类或接口
- 具体产品(ConcreteProduct):实现抽象产品接口的具体类
2.2 UML类图示例
code复制+-------------------+ +-------------------+ +-------------------+
| <<interface>> | | | | |
| Product | | Factory | | ConcreteProductA |
+-------------------+ +-------------------+ +-------------------+
| +use(): void | | +createProduct(): | | +use(): void |
+-------------------+ | Product | +-------------------+
^ +-------------------+ ^
| | |
| | |
+----------------------------+---------------------------+
2.3 代码实现示例
java复制// 抽象产品
interface Shape {
void draw();
}
// 具体产品
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
// 工厂类
class ShapeFactory {
public static Shape createShape(String type) {
if ("circle".equalsIgnoreCase(type)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(type)) {
return new Rectangle();
}
throw new IllegalArgumentException("未知的形状类型");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("circle");
circle.draw();
Shape rectangle = ShapeFactory.createShape("rectangle");
rectangle.draw();
}
}
3. 隐藏创建逻辑的实现原理
3.1 封装变化点
简单工厂模式的核心优势在于它将对象创建这一"变化点"封装在工厂类中。当需要新增产品类型时,只需修改工厂类,客户端代码无需任何改动,符合开闭原则(对扩展开放,对修改关闭)。
3.2 创建逻辑的隔离
通过工厂方法,客户端完全不需要知道:
- 具体产品类是如何实现的
- 对象创建需要哪些参数
- 对象创建过程中是否有特殊处理(如缓存、池化等)
这种隔离使得客户端代码更加简洁,也降低了系统的耦合度。
3.3 配置化的扩展
在实际项目中,我们还可以将产品类型与具体类的映射关系配置在外部(如properties文件、数据库或Spring配置中),实现完全的可配置化:
java复制class ConfigurableFactory {
private static Map<String, Class<? extends Product>> productMap = new HashMap<>();
static {
// 从配置加载映射关系
productMap.put("typeA", ProductA.class);
productMap.put("typeB", ProductB.class);
}
public static Product createProduct(String type) {
try {
return productMap.get(type).newInstance();
} catch (Exception e) {
throw new RuntimeException("创建产品失败", e);
}
}
}
4. 简单工厂模式的实际应用场景
4.1 日志记录器
不同的环境(开发、测试、生产)可能需要不同的日志记录方式(控制台输出、文件记录、数据库存储等)。使用简单工厂模式可以根据环境配置返回适当的日志记录器。
java复制interface Logger {
void log(String message);
}
class FileLogger implements Logger {
@Override
public void log(String message) {
// 写入文件
}
}
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println(message);
}
}
class LoggerFactory {
public static Logger getLogger(String env) {
if ("prod".equals(env)) {
return new FileLogger();
} else {
return new ConsoleLogger();
}
}
}
4.2 数据库连接
不同的数据库类型(MySQL、Oracle、PostgreSQL等)需要不同的连接方式。简单工厂可以隐藏这些差异:
java复制interface Connection {
void connect();
}
class MySQLConnection implements Connection {
@Override
public void connect() {
// MySQL连接逻辑
}
}
class OracleConnection implements Connection {
@Override
public void connect() {
// Oracle连接逻辑
}
}
class ConnectionFactory {
public static Connection createConnection(String dbType) {
switch (dbType.toLowerCase()) {
case "mysql": return new MySQLConnection();
case "oracle": return new OracleConnection();
default: throw new IllegalArgumentException("不支持的数据库类型");
}
}
}
4.3 支付网关集成
电商平台需要支持多种支付方式(支付宝、微信支付、银联等),每种支付方式的接口和参数都不相同:
java复制interface Payment {
boolean pay(double amount);
}
class Alipay implements Payment {
@Override
public boolean pay(double amount) {
// 调用支付宝接口
return true;
}
}
class WechatPay implements Payment {
@Override
public boolean pay(double amount) {
// 调用微信支付接口
return true;
}
}
class PaymentFactory {
public static Payment createPayment(String paymentType) {
if ("alipay".equalsIgnoreCase(paymentType)) {
return new Alipay();
} else if ("wechat".equalsIgnoreCase(paymentType)) {
return new WechatPay();
}
throw new IllegalArgumentException("不支持的支付方式");
}
}
5. 简单工厂模式的进阶应用与优化
5.1 使用反射消除条件判断
当产品类型很多时,工厂类中的条件判断会变得冗长。可以使用反射机制动态创建对象:
java复制class ReflectiveFactory {
public static Product createProduct(String className) {
try {
Class<?> clazz = Class.forName(className);
return (Product) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("创建产品失败", e);
}
}
}
5.2 结合单例模式
如果产品对象是无状态的或创建成本很高,可以结合单例模式:
java复制class SingletonFactory {
private static final Map<String, Product> instances = new HashMap<>();
public static synchronized Product getProduct(String type) {
if (!instances.containsKey(type)) {
switch (type) {
case "A": instances.put(type, new ProductA());
case "B": instances.put(type, new ProductB());
default: throw new IllegalArgumentException();
}
}
return instances.get(type);
}
}
5.3 使用枚举简化实现
对于固定的、有限的产品类型,可以使用枚举来实现简单工厂:
java复制enum ProductType {
TYPE_A {
@Override
public Product create() {
return new ProductA();
}
},
TYPE_B {
@Override
public Product create() {
return new ProductB();
}
};
public abstract Product create();
}
// 使用方式
Product product = ProductType.TYPE_A.create();
6. 简单工厂模式的局限性
虽然简单工厂模式有很多优点,但它也存在一些局限性:
- 违反开闭原则:当需要新增产品类型时,必须修改工厂类的代码
- 工厂类职责过重:随着产品类型的增加,工厂类会变得庞大复杂
- 难以扩展:不支持产品族的创建(如需要创建相关联的一组对象)
对于更复杂的场景,可以考虑使用工厂方法模式或抽象工厂模式。简单工厂模式最适合产品类型相对固定,且不太可能频繁变化的场景。
7. 实际项目中的经验分享
7.1 性能优化技巧
在需要频繁创建对象的场景中,可以考虑使用对象池技术:
java复制class PooledFactory {
private static final Map<String, Queue<Product>> pools = new HashMap<>();
static {
pools.put("A", new LinkedList<>(Arrays.asList(new ProductA(), new ProductA())));
pools.put("B", new LinkedList<>(Arrays.asList(new ProductB(), new ProductB())));
}
public static Product getProduct(String type) {
Queue<Product> pool = pools.get(type);
if (pool == null || pool.isEmpty()) {
return createNewProduct(type);
}
return pool.poll();
}
public static void returnProduct(Product product) {
// 根据产品类型返回对应的池
}
private static Product createNewProduct(String type) {
// 创建新产品的逻辑
}
}
7.2 异常处理建议
在工厂方法中,良好的异常处理非常重要:
java复制public static Product createProduct(String type) {
Objects.requireNonNull(type, "产品类型不能为null");
try {
switch (type.toLowerCase()) {
case "a": return new ProductA();
case "b": return new ProductB();
default: throw new IllegalArgumentException("未知的产品类型: " + type);
}
} catch (Exception e) {
throw new ProductCreationException("创建产品失败", e);
}
}
7.3 测试注意事项
测试工厂类时,需要关注:
- 边界条件测试:传入null、空字符串、未知类型等
- 性能测试:高频调用时的表现
- 并发测试:多线程环境下的线程安全性
java复制@Test
public void testCreateProductWithNullType() {
assertThrows(NullPointerException.class, () -> {
ProductFactory.createProduct(null);
});
}
@Test
public void testCreateProductWithUnknownType() {
assertThrows(IllegalArgumentException.class, () -> {
ProductFactory.createProduct("unknown");
});
}
8. 与其他创建型模式的对比
8.1 与工厂方法模式比较
简单工厂模式与工厂方法模式的主要区别在于:
- 简单工厂:一个工厂类负责所有产品的创建
- 工厂方法:每个产品有对应的工厂类,通过子类决定实例化哪个类
8.2 与抽象工厂模式比较
抽象工厂模式用于创建产品族,而简单工厂模式只创建单一产品:
- 简单工厂:创建单一产品(如只创建按钮)
- 抽象工厂:创建相关联的一组产品(如创建整套UI组件:按钮、文本框、下拉框等)
8.3 与建造者模式比较
建造者模式更关注复杂对象的构建过程,而简单工厂模式关注的是对象的创建:
- 简单工厂:直接返回完整的产品对象
- 建造者:分步骤构建复杂对象,最后返回完整对象
9. 在常见框架中的应用
9.1 Spring框架中的BeanFactory
Spring的核心容器BeanFactory本质上是一个增强版的工厂模式实现,它:
- 通过配置文件定义bean及其依赖关系
- 支持单例/原型作用域
- 提供生命周期管理
- 支持AOP等高级特性
9.2 JDK中的Calendar.getInstance()
Java标准库中的Calendar类使用了简单工厂模式:
java复制Calendar calendar = Calendar.getInstance();
// 实际上根据Locale和TimeZone返回不同的Calendar子类
9.3 Log4j/Logback中的LoggerFactory
日志框架通常使用工厂模式创建Logger实例:
java复制Logger logger = LoggerFactory.getLogger(MyClass.class);
// 根据配置返回适当的Logger实现
10. 设计原则与最佳实践
10.1 遵循的设计原则
简单工厂模式体现了以下几个重要的面向对象设计原则:
- 单一职责原则:将对象创建的逻辑集中在一个地方
- 开闭原则:对扩展开放(可以新增产品类),对修改关闭(不修改客户端代码)
- 依赖倒置原则:客户端依赖抽象(Product接口),而非具体实现
10.2 最佳实践建议
- 工厂方法命名:使用createXxx()或getXxx()等直观的命名方式
- 产品接口设计:产品接口应该足够抽象,能够涵盖所有具体产品的共性
- 文档注释:详细记录每个产品类型的用途和创建条件
- 错误处理:对无效的类型参数提供清晰的错误信息
- 性能考虑:对于创建成本高的对象,考虑使用缓存或对象池
10.3 反模式警示
- 万能工厂:一个工厂类创建完全不相关的多种产品(违反单一职责)
- 条件爆炸:工厂方法中包含过多的if-else分支(考虑使用策略模式或反射)
- 隐藏依赖:产品构造函数需要特定参数但工厂方法没有暴露
- 过度设计:在简单场景中使用工厂模式反而增加了复杂度
在实际项目中应用简单工厂模式时,需要权衡其带来的好处和引入的复杂度,确保模式的引入确实解决了实际问题,而不是为了使用模式而使用模式。
