还在用if-else创建对象?还在为代码耦合头疼?本文带你深入理解工厂方法模式,通过汽车工厂的生动比喻和Java实战代码,教你如何优雅地实现对象创建与使用分离。从日志系统到支付系统,真实案例助你掌握这一设计模式的精髓,让代码更易维护、更易扩展!
工厂方法模式详解
一、模式概述
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
核心思想
工厂方法模式的核心思想是将对象的创建和使用分离,通过工厂类来创建对象,客户端只需要使用工厂类来获取对象,而不需要关心对象的具体创建过程。
类比
为了更好地理解工厂方法模式,我们可以用汽车制造工厂来比喻:
没有工厂方法时(传统方式): 就像客户直接去汽车生产车间,自己动手组装汽车。客户需要知道汽车的所有零件在哪里、如何组装、需要什么工具等。如果客户想要不同类型的汽车(轿车、SUV、跑车),客户需要分别学习每种汽车的组装方法。这样不仅麻烦,而且客户需要掌握大量专业知识。
有工厂方法时: 就像客户去4S店买车,只需要告诉销售员想要什么类型的汽车,销售员就会从工厂调来相应的汽车交付给客户。客户不需要知道汽车是如何制造的,只需要说"我要一辆轿车"或"我要一辆SUV"。4S店就像工厂,销售员就像工厂方法,客户通过销售员获取汽车,而不需要关心制造过程。
工厂方法模式的优势:
- 解耦:客户不需要知道汽车怎么造的,只需要知道想要什么类型的车
- 扩展性:如果工厂推出新车型(比如电动车),只需要新增一个电动车工厂,客户使用方式不变
- 统一接口:无论什么类型的汽车,客户都是通过同样的方式(告诉销售员)来获取
适用场景
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化时
二、模式结构
类图
┌─────────────────┐│ Product ││─────────────────││ + operation() │└─────────────────┘ △ │ │┌─────────────────┐│ ConcreteProduct ││─────────────────││ + operation() │└─────────────────┘┌─────────────────┐ ┌─────────────────┐│ Creator │ │ ConcreteCreator ││─────────────────│ │─────────────────││ + factoryMethod()│──────│ + factoryMethod()││ + anOperation() │ └─────────────────┘└─────────────────┘
角色说明
- Product(产品):定义工厂方法所创建的对象的接口
- ConcreteProduct(具体产品):实现Product接口
- Creator(创建者):声明工厂方法,该方法返回一个Product类型的对象
- ConcreteCreator(具体创建者):重定义工厂方法以返回一个ConcreteProduct实例
三、代码实现
Java实现
// 产品接口interfaceProduct { String operation();}// 具体产品AclassConcreteProductAimplementsProduct {@Overridepublic String operation() {return"具体产品A的操作"; }}// 具体产品BclassConcreteProductBimplementsProduct {@Overridepublic String operation() {return"具体产品B的操作"; }}// 创建者抽象类abstractclassCreator {publicabstract Product factoryMethod();public String someOperation() {Productproduct= factoryMethod();return"创建者: " + product.operation(); }}// 具体创建者AclassConcreteCreatorAextendsCreator {@Overridepublic Product factoryMethod() {returnnewConcreteProductA(); }}// 具体创建者BclassConcreteCreatorBextendsCreator {@Overridepublic Product factoryMethod() {returnnewConcreteProductB(); }}// 客户端代码publicclassClient {publicstaticvoidmain(String[] args) { System.out.println("使用具体创建者A:"); clientCode(newConcreteCreatorA()); System.out.println("\n使用具体创建者B:"); clientCode(newConcreteCreatorB()); }publicstaticvoidclientCode(Creator creator) { System.out.println(creator.someOperation()); }}
四、实际应用案例
日志记录器工厂
import java.io.*;import java.text.SimpleDateFormat;import java.util.Date;// 日志记录器产品接口interfaceLogger {voidlog(String message);}// 文件日志记录器classFileLoggerimplementsLogger {private String filename;publicFileLogger(String filename) {this.filename = filename; }@Overridepublicvoidlog(String message) {try (PrintWriterwriter=newPrintWriter(newFileWriter(filename, true))) {SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"); writer.println("[" + sdf.format(newDate()) + "] " + message); } catch (IOException e) { e.printStackTrace(); } }}// 控制台日志记录器classConsoleLoggerimplementsLogger {@Overridepublicvoidlog(String message) {SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("[" + sdf.format(newDate()) + "] " + message); }}// 数据库日志记录器classDatabaseLoggerimplementsLogger {private String connectionString;publicDatabaseLogger(String connectionString) {this.connectionString = connectionString; }@Overridepublicvoidlog(String message) {SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("[数据库日志] [" + sdf.format(newDate()) + "] " + message + " (连接: " + connectionString + ")"); }}// 日志工厂abstractclassLoggerFactory {publicabstract Logger createLogger();}// 文件日志工厂classFileLoggerFactoryextendsLoggerFactory {private String filename;publicFileLoggerFactory(String filename) {this.filename = filename; }@Overridepublic Logger createLogger() {returnnewFileLogger(filename); }}// 控制台日志工厂classConsoleLoggerFactoryextendsLoggerFactory {@Overridepublic Logger createLogger() {returnnewConsoleLogger(); }}// 数据库日志工厂classDatabaseLoggerFactoryextendsLoggerFactory {private String connectionString;publicDatabaseLoggerFactory(String connectionString) {this.connectionString = connectionString; }@Overridepublic Logger createLogger() {returnnewDatabaseLogger(connectionString); }}// 使用示例publicclassLoggerFactoryDemo {publicstaticvoidmain(String[] args) {// 使用文件日志LoggerFactoryfileFactory=newFileLoggerFactory("app.log");LoggerfileLogger= fileFactory.createLogger(); fileLogger.log("应用程序启动"); fileLogger.log("处理用户请求");// 使用控制台日志LoggerFactoryconsoleFactory=newConsoleLoggerFactory();LoggerconsoleLogger= consoleFactory.createLogger(); consoleLogger.log("调试信息");// 使用数据库日志LoggerFactorydbFactory=newDatabaseLoggerFactory("localhost:5432/mydb");LoggerdbLogger= dbFactory.createLogger(); dbLogger.log("用户登录"); }}
支付系统工厂
// 支付方式接口interfacePaymentMethod { String pay(double amount);}// 支付宝支付classAlipayPaymentimplementsPaymentMethod {@Overridepublic String pay(double amount) { System.out.println("使用支付宝支付 " + amount + " 元");return"支付宝支付成功"; }}// 微信支付classWechatPaymentimplementsPaymentMethod {@Overridepublic String pay(double amount) { System.out.println("使用微信支付 " + amount + " 元");return"微信支付成功"; }}// 银行卡支付classBankCardPaymentimplementsPaymentMethod {private String cardNumber;publicBankCardPayment(String cardNumber) {this.cardNumber = cardNumber; }@Overridepublic String pay(double amount) { System.out.println("使用银行卡 " + cardNumber + " 支付 " + amount + " 元");return"银行卡支付成功"; }}// 支付工厂abstractclassPaymentFactory {publicabstract PaymentMethod createPayment();}// 支付宝工厂classAlipayFactoryextendsPaymentFactory {@Overridepublic PaymentMethod createPayment() {returnnewAlipayPayment(); }}// 微信支付工厂classWechatFactoryextendsPaymentFactory {@Overridepublic PaymentMethod createPayment() {returnnewWechatPayment(); }}// 银行卡工厂classBankCardFactoryextendsPaymentFactory {private String cardNumber;publicBankCardFactory(String cardNumber) {this.cardNumber = cardNumber; }@Overridepublic PaymentMethod createPayment() {returnnewBankCardPayment(cardNumber); }}// 使用示例publicclassPaymentFactoryDemo {publicstaticvoidprocessPayment(PaymentFactory factory, double amount) {PaymentMethodpayment= factory.createPayment();Stringresult= payment.pay(amount); System.out.println(result); }publicstaticvoidmain(String[] args) { System.out.println("=== 支付系统示例 ===");// 支付宝支付 System.out.println("\n1. 支付宝支付:"); processPayment(newAlipayFactory(), 100);// 微信支付 System.out.println("\n2. 微信支付:"); processPayment(newWechatFactory(), 200);// 银行卡支付 System.out.println("\n3. 银行卡支付:"); processPayment(newBankCardFactory("6222 **** **** 1234"), 300); }}
五、有工厂方法与没有工厂方法的对比
没有工厂方法(传统方式)
代码示例:
// 产品接口interfaceLogger {voidlog(String message);}// 文件日志记录器classFileLoggerimplementsLogger {private String filename;publicFileLogger(String filename) {this.filename = filename; }@Overridepublicvoidlog(String message) { System.out.println("写入文件: " + message); }}// 控制台日志记录器classConsoleLoggerimplementsLogger {@Overridepublicvoidlog(String message) { System.out.println("输出到控制台: " + message); }}// 客户端代码publicclassClientWithoutFactory {publicstaticvoidmain(String[] args) {// 客户端需要直接创建具体产品Loggerlogger1=newFileLogger("app.log"); logger1.log("应用程序启动");Loggerlogger2=newConsoleLogger(); logger2.log("调试信息");// 如果要切换日志方式,需要修改客户端代码// 比如从文件日志切换到控制台日志,需要修改 new FileLogger() 为 new ConsoleLogger() }}
存在的问题:
- 客户端与具体产品强耦合:客户端必须知道具体产品类(FileLogger、ConsoleLogger)的名称
- 代码重复:如果多个地方需要创建相同产品,创建逻辑会重复出现
有工厂方法(工厂方法模式)
代码示例:
// 产品接口interfaceLogger {voidlog(String message);}// 文件日志记录器classFileLoggerimplementsLogger {private String filename;publicFileLogger(String filename) {this.filename = filename; }@Overridepublicvoidlog(String message) { System.out.println("写入文件: " + message); }}// 控制台日志记录器classConsoleLoggerimplementsLogger {@Overridepublicvoidlog(String message) { System.out.println("输出到控制台: " + message); }}// 工厂抽象类abstractclassLoggerFactory {publicabstract Logger createLogger();}// 文件日志工厂classFileLoggerFactoryextendsLoggerFactory {private String filename;publicFileLoggerFactory(String filename) {this.filename = filename; }@Overridepublic Logger createLogger() {returnnewFileLogger(filename); }}// 控制台日志工厂classConsoleLoggerFactoryextendsLoggerFactory {@Overridepublic Logger createLogger() {returnnewConsoleLogger(); }}// 客户端代码publicclassClientWithFactory {publicstaticvoidmain(String[] args) {// 客户端只需要与工厂接口交互LoggerFactoryfactory=newFileLoggerFactory("app.log");Loggerlogger= factory.createLogger(); logger.log("应用程序启动");// 切换日志方式,只需要更换工厂实现 factory = newConsoleLoggerFactory(); logger = factory.createLogger(); logger.log("调试信息");// 新增产品类型时,客户端代码完全不需要修改 }}
带来的优势:
- 解耦:客户端只需要知道工厂接口,不需要知道具体产品类
- 符合开闭原则:新增产品类型只需要新增工厂类,不需要修改现有代码
- 代码复用:创建逻辑集中在工厂类中,可以在多个地方复用
- 统一接口:所有产品都通过工厂方法创建,使用方式一致
对比总结表
| | |
|---|
| 客户端耦合度 | | |
| 扩展性 | | |
| 代码复用 | | |
| 符合开闭原则 | | |
| 维护成本 | | |
| 测试难度 | | |
| 类数量 | | |
| 代码复杂度 | | |
实际场景对比
场景:电商平台需要支持多种支付方式
没有工厂方法:
publicclassOrderService {publicvoidpay(String paymentType, double amount) {if (paymentType.equals("alipay")) {AlipayPaymentpayment=newAlipayPayment(); payment.pay(amount); } elseif (paymentType.equals("wechat")) {WechatPaymentpayment=newWechatPayment(); payment.pay(amount); } elseif (paymentType.equals("bankcard")) {BankCardPaymentpayment=newBankCardPayment("6222****1234"); payment.pay(amount); }// 每次新增支付方式,都要修改这个方法 }}
有工厂方法:
publicclassOrderService {private PaymentFactory paymentFactory;publicOrderService(PaymentFactory paymentFactory) {this.paymentFactory = paymentFactory; }publicvoidpay(double amount) {PaymentMethodpayment= paymentFactory.createPayment(); payment.pay(amount); }// 新增支付方式,只需要新增工厂类,不需要修改OrderService}// 使用时OrderServiceorderService=newOrderService(newAlipayFactory());orderService.pay(100); // 使用支付宝支付orderService = newOrderService(newWechatFactory());orderService.pay(200); // 使用微信支付
结论:工厂方法模式通过引入工厂层,将对象的创建与使用分离,使得系统更加灵活、可扩展、易维护。虽然增加了一些类的数量,但换来的是更好的架构设计。
六、优缺点分析
优点
- 符合开闭原则:可以在不修改代码的情况下引入新产品类型
- 符合单一职责原则:将产品创建代码放在子类中,主类不需要处理产品创建
- 解耦:客户端不需要知道具体产品的类名,只需要知道对应的工厂
缺点
- 类的数量增加:每增加一个产品,就需要增加一个具体产品类和具体工厂类
- 代码复杂度增加:引入了抽象层,增加了系统的理解难度
七、与其他模式的关系
- 与简单工厂模式:工厂方法模式是简单工厂模式的进一步抽象和推广
- 与抽象工厂模式:抽象工厂模式经常使用工厂方法模式来创建产品
- 与原型模式:原型模式通过克隆对象来创建新对象,而工厂方法模式通过创建新对象来实例化
- 与单例模式:工厂方法模式可以结合单例模式,确保工厂实例的唯一性
八、总结
工厂方法模式是一种非常实用的创建型设计模式,它通过定义创建对象的接口,让子类决定实例化哪个类,从而实现了创建和使用的解耦。在实际开发中,工厂方法模式广泛应用于各种需要灵活创建对象的场景,如日志系统、支付系统、数据库连接管理等。
使用工厂方法模式时,需要权衡其带来的灵活性和增加的复杂度。在产品类型较少且稳定的情况下,简单工厂模式可能更合适;而在产品类型较多且可能频繁扩展的情况下,工厂方法模式则是更好的选择。