本文为设计模式系列第5篇,聚焦创建型模式中的工厂方法模式,涵盖定义、原理、实际业务场景、优缺点、最佳实践及详细代码示例,适合系统学习与实战应用。
目录
1. 模式概述
工厂方法模式(Factory Method Pattern)是一种创建型设计模式。它通过定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类中进行,便于扩展和解耦。
1.1 定义
为创建对象定义一个接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
1.2 目的
- 封装对象创建,隐藏实现细节
- 支持扩展,便于新增产品类型
- 解耦客户端与具体产品,提升灵活性
2. 使用场景
工厂方法模式在实际开发中应用广泛,常见场景包括:
- 日志系统:如支持多种日志输出(文件、数据库、远程等),通过工厂方法灵活切换日志实现。
- 数据库访问:如支持多种数据库(MySQL、Oracle、SQL Server等),通过工厂方法屏蔽底层差异。
- UI组件库:如跨平台UI库,工厂方法根据平台创建不同风格的控件。
- 文档处理:如支持多种文档格式(PDF、Word、Excel),通过工厂方法创建对应处理器。
真实业务背景举例:
- 大型企业日志平台,需支持本地文件、数据库、远程服务器等多种日志存储方式,便于运维和审计。
- SaaS平台根据客户定制不同UI风格,工厂方法可灵活切换控件实现。
- 金融系统需对接多种数据库,工厂方法屏蔽底层实现,提升可维护性。
3. 优缺点分析
3.1 优点
- 封装性:隐藏对象创建细节,客户端只需依赖工厂接口。
- 扩展性:易于新增产品类型,符合开闭原则。
- 解耦性:客户端与具体产品解耦,便于维护和测试。
3.2 缺点
- 类数量增加:每新增一种产品都需对应工厂类,系统结构变复杂。
- 继承局限:扩展需继承工厂接口,可能导致继承层次过深。
- 使用限制:只适用于同族产品,不支持跨族产品创建。
4. 实际应用案例
- 日志系统:文件日志、数据库日志、远程日志等多种实现,便于扩展和切换。
- 数据库连接:根据配置创建不同数据库连接对象,屏蔽底层差异。
- UI控件工厂:根据操作系统或主题创建不同风格控件。
- 支付渠道对接:如微信、支付宝、银联等,每种渠道对应不同工厂。
5. 结构与UML类图
@startuml
package "Factory Method Pattern" #DDDDDD {
interface Product {
+ use(): void
}
class ConcreteProductA implements Product
class ConcreteProductB implements Product
interface Factory {
+ createProduct(): Product
}
class ConcreteFactoryA implements Factory
class ConcreteFactoryB implements Factory
class Client
Factory <|.. ConcreteFactoryA
Factory <|.. ConcreteFactoryB
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
ConcreteFactoryA --> ConcreteProductA : createProduct()
ConcreteFactoryB --> ConcreteProductB : createProduct()
Client ..> Factory : uses
}
@enduml
6. 代码示例
6.1 基本结构示例
业务背景: 企业日志平台需支持多种日志实现,便于灵活切换和扩展。
// 日志产品接口,定义日志操作
public interface Logger {
/**
* 写入日志信息
* @param message 日志内容
*/
void log(String message);
}
// 文件日志实现,写入本地文件
public class FileLogger implements Logger {
private String filePath;
public FileLogger(String filePath) {
this.filePath = filePath;
}
@Override
public void log(String message) {
// 实际业务:将日志写入指定文件
try (FileWriter writer = new FileWriter(filePath, true)) {
writer.write(message + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 数据库日志实现,写入数据库表
public class DatabaseLogger implements Logger {
private Connection connection;
public DatabaseLogger(Connection connection) {
this.connection = connection;
}
@Override
public void log(String message) {
// 实际业务:将日志写入数据库
try {
PreparedStatement stmt = connection.prepareStatement(
"INSERT INTO logs (message, timestamp) VALUES (?, ?)"
);
stmt.setString(1, message);
stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 日志工厂接口,定义创建Logger的方法
public interface LoggerFactory {
Logger createLogger();
}
// 文件日志工厂,负责创建FileLogger
public class FileLoggerFactory implements LoggerFactory {
private String filePath;
public FileLoggerFactory(String filePath) {
this.filePath = filePath;
}
@Override
public Logger createLogger() {
return new FileLogger(filePath);
}
}
// 数据库日志工厂,负责创建DatabaseLogger
public class DatabaseLoggerFactory implements LoggerFactory {
private Connection connection;
public DatabaseLoggerFactory(Connection connection) {
this.connection = connection;
}
@Override
public Logger createLogger() {
return new DatabaseLogger(connection);
}
}
// 客户端示例
public class Client {
public static void main(String[] args) throws Exception {
// 创建文件日志工厂
LoggerFactory fileFactory = new FileLoggerFactory("app.log");
Logger fileLogger = fileFactory.createLogger();
fileLogger.log("写入文件日志");
// 创建数据库日志工厂
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
LoggerFactory dbFactory = new DatabaseLoggerFactory(conn);
Logger dbLogger = dbFactory.createLogger();
dbLogger.log("写入数据库日志");
}
// 总结:通过工厂方法,客户端无需关心Logger的具体实现,便于扩展和维护。
}
6.2 实际业务场景:支付渠道工厂
业务背景: 电商平台需对接多种支付渠道(如微信、支付宝、银联),通过工厂方法灵活扩展。
// 支付接口,定义支付操作
public interface Payment {
void pay(double amount);
}
// 微信支付实现
public class WeChatPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("[WeChat] 支付 " + amount + " 元");
}
}
// 支付宝支付实现
public class AliPayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("[AliPay] 支付 " + amount + " 元");
}
}
// 银联支付实现
public class UnionPayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("[UnionPay] 支付 " + amount + " 元");
}
}
// 支付工厂接口
public interface PaymentFactory {
Payment createPayment();
}
// 微信支付工厂
public class WeChatPaymentFactory implements PaymentFactory {
public Payment createPayment() {
return new WeChatPayment();
}
}
// 支付宝支付工厂
public class AliPayPaymentFactory implements PaymentFactory {
public Payment createPayment() {
return new AliPayPayment();
}
}
// 银联支付工厂
public class UnionPayPaymentFactory implements PaymentFactory {
public Payment createPayment() {
return new UnionPayPayment();
}
}
// 客户端示例
public class PaymentClient {
public static void main(String[] args) {
PaymentFactory factory = new WeChatPaymentFactory();
Payment payment = factory.createPayment();
payment.pay(100.0);
// 切换支付渠道只需更换工厂实现
factory = new AliPayPaymentFactory();
payment = factory.createPayment();
payment.pay(200.0);
}
}
7. 测试用例
业务背景: 验证日志工厂和支付工厂的多实现切换与功能正确性。
public class FactoryMethodPatternTest {
@Test
public void testFileLogger() {
LoggerFactory factory = new FileLoggerFactory("test.log");
Logger logger = factory.createLogger();
logger.log("Test message");
// 验证文件内容
try {
List<String> lines = Files.readAllLines(Paths.get("test.log"));
assertTrue(lines.contains("Test message"));
} catch (IOException e) {
fail("Failed to read log file");
}
}
@Test
public void testDatabaseLogger() {
Connection conn = createTestConnection();
LoggerFactory factory = new DatabaseLoggerFactory(conn);
Logger logger = factory.createLogger();
logger.log("Test message");
// 验证数据库记录
try {
PreparedStatement stmt = conn.prepareStatement(
"SELECT message FROM logs WHERE message = ?"
);
stmt.setString(1, "Test message");
ResultSet rs = stmt.executeQuery();
assertTrue(rs.next());
} catch (SQLException e) {
fail("Failed to verify database log");
}
}
@Test
public void testPaymentFactory() {
PaymentFactory factory = new WeChatPaymentFactory();
Payment payment = factory.createPayment();
assertTrue(payment instanceof WeChatPayment);
factory = new AliPayPaymentFactory();
payment = factory.createPayment();
assertTrue(payment instanceof AliPayPayment);
}
private Connection createTestConnection() {
try {
return DriverManager.getConnection(
"jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1",
"sa",
""
);
} catch (SQLException e) {
throw new RuntimeException("Failed to create test connection", e);
}
}
}
8. 常见误区与反例
- 误区1:工厂类职责过重,变成"万能工厂"。
应按产品族拆分工厂,避免单一工厂膨胀。 - 误区2:客户端仍直接依赖具体产品。
应通过工厂接口获取产品,避免高耦合。 - 反例:工厂方法未能隐藏实现细节。
工厂应只暴露必要接口,隐藏创建逻辑。
9. 最佳实践
- 接口设计:工厂和产品接口要简洁,便于扩展和维护。
- 工厂拆分:按产品族或业务领域拆分工厂,避免"万能工厂"。
- 异常与资源管理:工厂方法应妥善处理异常和资源释放。
- 扩展性:新增产品类型时优先扩展工厂,不修改已有代码。
- 文档和UML同步:保持文档、UML和代码示例一致,便于团队协作。
10. 参考资料与延伸阅读
- 《设计模式:可复用面向对象软件的基础》GoF
- Effective Java(中文版)
- https://refactoringguru.cn/design-patterns/factory-method
- https://www.baeldung.com/java-factory-pattern
本文为设计模式系列第5篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。