日常设计模式(2):对象的创建不再烦恼:工厂模式的“魔法”

75 阅读13分钟

你是否曾被对象创建的繁琐细节所困扰?今天就让我们一起探索工厂模式,看它如何帮你化繁为简!

一、前序:生活中无处不在的"工厂"

周末,我站在星巴克的柜台前。

"一杯焦糖玛奇朵,中杯,少冰。"

几分钟后,一杯香气四溢的咖啡便出现在我手中。在这个过程中,我只需告知咖啡名称和一些简单的定制要求,无需了解牛奶的比例、咖啡豆的研磨程度,甚至不需要亲自操作复杂的咖啡机。

这不正是工厂模式的生动体现吗?

再想想手机APP上点外卖、在4S店提车、在IKEA下单一张桌子...这些场景中,我们都在扮演"客户端"的角色,只需发出请求、表达需求,而具体的生产过程则被巧妙封装,由专业的"工厂"代为处理。

作为程序员,我们不也经常面临类似的问题吗?如何让对象的创建变得简单而优雅?如何在保持代码灵活性的同时,避免创建对象时的各种依赖耦合?

这正是工厂模式要解决的核心问题。

日常开发中,我们创建对象的方式往往都比较直接,就像下面这样:

// 处理支付宝支付
if ("alipay".equals(payType)) {
AlipayService alipayService = new AlipayService();
    alipayService.setProp1("支付宝特有属性");
    return alipayService.doPay(order);
}
// 处理微信支付
        else if ("wechat".equals(payType)) {
WechatPayService wechatPayService = new WechatPayService();
    wechatPayService.setAppId("wx123456");
    return wechatPayService.pay(order);
}
// 处理银联支付
        else if ("unionpay".equals(payType)) {
UnionPayService unionPayService = new UnionPayService();
    unionPayService.setMerchantId("merchant123");
    return unionPayService.process(order);
}
// 可能还有更多支付方式...

这种方式有什么大问题嘛?其实好像也没什么问题,在有些时候反而会更简洁明了,但是在一些复杂场景或复杂业务中我们就不好对其进行业务扩展了。

这种直接创建对象并进行条件判断的方式,看似简单,实则埋下了不少隐患::

  1. 高耦合:客户端代码与具体实现类紧密耦合

  2. 难以维护:每增加一种支付方式,就需要修改这段判断逻辑

  3. 代码重复:如果多个地方需要创建支付服务对象,这些判断逻辑就会重复出现

  4. 违反开闭原则:对扩展开放,对修改关闭的原则被违反

  5. 测试困难:条件判断逻辑增加了单元测试的复杂性

这时,我们就需要一种更优雅的设计模式来解决这个问题。

二、工厂模式:创建型设计模式中的明星

2.1 什么是工厂模式?

工厂模式(Factory Pattern)是创建型设计模式的一种,它提供了一种创建对象的最佳方式,将对象的创建与使用分离,让对象的创建变得优雅。

在GoF(Gang of Four,四人帮)的设计模式中,工厂模式主要分为三类:

  • 简单工厂模式(Simple Factory Pattern)
  • 工厂方法模式(Factory Method Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)

这三种模式像是工厂的三个进化阶段,随着复杂度的增加而逐步升级。

2.2 简单工厂模式:万物起源

简单工厂模式,顾名思义,是最简单的工厂模式。它通过一个工厂类根据传入的参数不同返回不同的实例,被创建的实例通常都具有共同的父类或接口。

来看一个例子:

// 通用支付接口
public interface Payment {
    void pay();
    void transfer();
}

// 微信
public class WxPayment implements Payment {
    @Override
    public void pay() {
        System.out.println("正在使用微信支付...");
    }
    
    @Override
    public void transfer() {
        System.out.println("正在使用微信转账...");
    }
}

// 支付宝
public class AliPayment implements Payment {
    @Override
    public void pay() {
        System.out.println("正在使用支付宝支付...");
    }
    
    @Override
    public void transfer() {
        System.out.println("正在使用支付宝转账...");
    }
}

// 简单工厂类
public class SimplePaymentFactory {
    public static Payment createPayment(String type) {
        if ("WEIXIN".equalsIgnoreCase(type)) {
            return new WxPayment();
        } else if ("ALIPAY".equalsIgnoreCase(type)) {
            return new AliPayment();
        }
        throw new RuntimeException("当前的支付类型不支持");
    }
}

客户端代码:

public class Client {
    public static void main(String[] args) {
        // 向工厂获取微信支付客户端
        WxPayment weixin = SimplePaymentFactory.createPayment("WEIXIN");
        weixin.pay();
        weixin.transfer();
        
        // 向工厂获取阿里支付客户端
        AliPayment alipay = SimplePaymentFactory.createPayment("ALIPAY");
        alipay.pay();
        alipay.transfer();
    }
}

简单工厂的特点:

  1. 将创建对象的代码集中在一个工厂类中,客户端无需关心对象的创建细节
  2. 客户端只需要知道所需对象的名称即可
  3. 如果需要添加新的产品,需要修改工厂类,违反了"开闭原则"

2.3 工厂方法模式:多态的魅力

简单工厂的最大问题是:每增加一个产品,都需要修改工厂类。如果产品种类非常多,工厂类的代码将变得臃肿且难以维护。

工厂方法模式通过定义一个创建对象的接口,让子类决定实例化哪一个类,工厂方法让类的实例化推迟到子类中进行。

// 支付工厂接口
public interface PayFactory {
    Payment createPayment();
}

// 微信支付工厂
public class WxPayFactory implements PayFactory {
    @Override
    public Payment createPayment() {
        return new WxPayment();
    }
}

// 阿里支付工厂
public class AliPayFactory implements PayFactory {
    @Override
    public Payment createPayment() {
        return new AliPayment();
    }
}

客户端代码:

public class Client {
    public static void main(String[] args) {
        //创建微信支付工厂
        PayFactory wxPayFactory = new WxPayFactory();
        Payment wxPayment = wxPayFactory.createPayment();
        wxPayment.pay();
        wxPayment.transfer();
        
        //创建支付宝支付工厂
        PayFactory aliPayFactory = new AliPayFactory();
        Payment aliPayment = aliPayFactory.createPayment();
        aliPayment.pay();
        aliPayment.transfer();
    }
}

工厂方法的特点:

  1. 符合开闭原则,新增产品只需添加对应的工厂类
  2. 每个产品都有对应的工厂类,增加了系统复杂度
  3. 更加灵活,但也带来了更多的类

2.4 抽象工厂模式:产品族的概念

当我们需要创建一系列相关或相互依赖的对象时,抽象工厂模式就派上用场了。它提供一个接口用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。

回顾一下,在日常支付对接过程中,每家支付商都有自己的配置文件、密钥以及URL等,我们的支付对接代码中,需要根据支付商类型来获取对应的配置文件、密钥以及URL等:


// 支付配置接口
public interface PayConfig {
    String getConfig();
}

// 微信支付配置
public class WxPayConfig implements PayConfig {
    @Override
    public String getConfig() {
        return "wx_config";
    }
}

// 支付配置接口
public class AliPayConfig implements PayConfig {
    @Override
    public String getConfig() {
        return "ali_config";
    }
}

// 抽象支付工厂接口
public interface PayFactory {
    PayConfig createPayConfig();
    Payment createPayment(PayConfig payConfig);
}

// 微信支付工厂
public class WxPayFactory implements PayFactory {
    @Override
    public PayConfig createPayConfig() {
        return new WxPayConfig();
    }
    
    @Override
    public Payment createPayment(PayConfig payConfig) {
        return new WxPayment(payConfig);
    }
}

// 阿里支付工厂接口
public class AliPayFactory implements PayFactory {
    @Override
    public PayConfig createPayConfig() {
        return new AliPayConfig();
    }
    
    @Override
    public Payment createPayment(PayConfig payConfig) {
        return new AliPayment(payConfig);
    }
}



客户端代码:

public class Client {
    public static void main(String[] args) {
        PayFactory wxPayFactory = new WxPayFactory();
        Payment wxPayment = wxPayFactory.createPayment(wxPayFactory.createPayConfig());
        wxPayment.pay();
        wxPayment.transfer();
        
        PayFactory aliPayFactory = new AliPayFactory();
        Payment aliPayment = aliPayFactory.createPayment(aliPayFactory.createPayConfig());
        aliPayment.pay();
        aliPayment.transfer();
    }
}

朋友们,抽象工厂模式优不优雅?

抽象工厂的特点:

  1. 可以确保一系列相关产品的一致性
  2. 符合开闭原则,但增加新产品族比较困难
  3. 适合系统产品有多个产品族,而系统只消费其中某一族的产品的情况

2.5 静态工厂方法:Java的特殊实践

在Java的标准库中,我们经常能看到一种特殊的工厂模式实现——静态工厂方法。它通过公共的静态方法返回类的实例,而不是通过构造器。

例如,Integer.valueOf()Calendar.getInstance()等方法都属于静态工厂方法。

// 自定义一个静态工厂方法
public class User {
    private String name;
    private int age;
    
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 静态工厂方法
    public static User createAdult(String name) {
        return new User(name, 18);
    }
    
    public static User createChild(String name) {
        return new User(name, 6);
    }
    
    // getter和setter
    // ...
}

客户端代码:

public class Client {
    public static void main(String[] args) {
        User adult = User.createAdult("张三");
        User child = User.createChild("李四");
    }
}

静态工厂方法的优势:

  1. 比构造器更有描述性,名称可以准确传达创建对象的意图
  2. 不必每次调用时都创建新对象,可以返回缓存的实例
  3. 可以返回任何子类型的对象,更灵活
  4. 可以根据参数不同而返回不同类的对象
  5. 简化了客户端代码

三、深入理解工厂模式

3.1 工厂模式的核心思想

工厂模式的核心思想可以概括为:"封装变化,针对接口编程,而不是针对实现编程"。

它解决了以下几个问题:

  1. 封装对象创建的复杂性:客户端不需要了解对象创建的具体细节
  2. 降低耦合度:将对象的创建与使用分离
  3. 增强扩展性:符合开闭原则,便于添加新的产品类型
  4. 提高代码可维护性:集中管理对象的创建逻辑

3.2 工厂模式的适用场景

  1. 当一个类不知道它所需要的对象的类时
  2. 当一个类希望由其子类来指定它所创建的对象时
  3. 当类将创建对象的职责委托给多个帮助子类之一,并且您希望将哪一个帮助子类是代理者这一信息局部化时

3.3 各种工厂模式的对比

模式优点缺点适用场景
简单工厂实现简单,客户端无需知道具体产品类违反开闭原则,工厂类职责过重产品种类较少且不常变化
工厂方法符合开闭原则,单一职责原则类的个数容易过多,增加系统复杂度产品种类多,需要灵活扩展
抽象工厂产品族概念,创建相关产品簇难以增加新的产品等级结构需要创建一系列相互关联的产品
静态工厂方法简单直观,代码量少不能继承,不能实现多态创建对象的方式比较固定

四、实战:设计一个支付工厂

在实际开发中,支付系统是工厂模式的一个典型应用场景。假设我们需要实现一个支持支付宝、微信支付、银联等多种支付方式的系统:

4.1 定义支付接口

/**
 * 支付接口
 */
public interface Payment {
    /**
     * 初始化支付环境
     */
    void init();
    
    /**
     * 支付操作
     * @param amount 支付金额
     * @return 支付结果
     */
    boolean pay(double amount);
}

4.2 实现各种支付方式

/**
 * 支付宝支付
 */
public class AliPay implements Payment {
    @Override
    public void init() {
        System.out.println("初始化支付宝支付环境...");
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("支付宝支付 " + amount + " 元");
        // 实际支付逻辑...
        return true;
    }
}

/**
 * 微信支付
 */
public class WeChatPay implements Payment {
    @Override
    public void init() {
        System.out.println("初始化微信支付环境...");
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("微信支付 " + amount + " 元");
        // 实际支付逻辑...
        return true;
    }
}

/**
 * 银联支付
 */
public class UnionPay implements Payment {
    @Override
    public void init() {
        System.out.println("初始化银联支付环境...");
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("银联卡支付 " + amount + " 元");
        // 实际支付逻辑...
        return true;
    }
}

4.3 使用工厂方法模式实现支付工厂

/**
 * 支付工厂接口
 */
public interface PaymentFactory {
    Payment createPayment();
}

/**
 * 支付宝工厂
 */
public class AliPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new AliPay();
    }
}

/**
 * 微信支付工厂
 */
public class WeChatPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new WeChatPay();
    }
}

/**
 * 银联支付工厂
 */
public class UnionPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new UnionPay();
    }
}

4.4 客户端使用

public class PaymentClient {
    public static void main(String[] args) {
        // 创建支付宝支付
        PaymentFactory aliPayFactory = new AliPayFactory();
        Payment aliPay = aliPayFactory.createPayment();
        aliPay.init();
        aliPay.pay(100.0);
        
        // 创建微信支付
        PaymentFactory weChatPayFactory = new WeChatPayFactory();
        Payment weChatPay = weChatPayFactory.createPayment();
        weChatPay.init();
        weChatPay.pay(200.0);
        
        // 创建银联支付
        PaymentFactory unionPayFactory = new UnionPayFactory();
        Payment unionPay = unionPayFactory.createPayment();
        unionPay.init();
        unionPay.pay(300.0);
    }
}

4.5 优化:结合简单工厂

我们还可以结合简单工厂模式,让系统更加简洁:

/**
 * 支付类型枚举
 */
public enum PaymentType {
    ALI_PAY,
    WECHAT_PAY,
    UNION_PAY
}

/**
 * 简单支付工厂
 */
public class SimplePaymentFactory {
    public static Payment createPayment(PaymentType type) {
        switch (type) {
            case ALI_PAY:
                return new AliPay();
            case WECHAT_PAY:
                return new WeChatPay();
            case UNION_PAY:
                return new UnionPay();
            default:
                throw new IllegalArgumentException("不支持的支付类型");
        }
    }
}

客户端使用:

public class PaymentClient {
    public static void main(String[] args) {
        // 创建支付宝支付
        Payment aliPay = SimplePaymentFactory.createPayment(PaymentType.ALI_PAY);
        aliPay.init();
        aliPay.pay(100.0);
        
        // 创建微信支付
        Payment weChatPay = SimplePaymentFactory.createPayment(PaymentType.WECHAT_PAY);
        weChatPay.init();
        weChatPay.pay(200.0);
        
        // 创建银联支付
        Payment unionPay = SimplePaymentFactory.createPayment(PaymentType.UNION_PAY);
        unionPay.init();
        unionPay.pay(300.0);
    }
}

4.6 反思与改进

在实际开发中,我们还可以结合配置文件或注解,使工厂模式更加灵活:

/**
 * 支付工厂(结合反射机制实现)
 */
public class ReflectPaymentFactory {
    private static Map<String, Class<? extends Payment>> paymentMap = new ConcurrentHashMap<>();
    
    // 在静态代码块中注册支付方式
    static {
        paymentMap.put("alipay", AliPay.class);
        paymentMap.put("wechat", WeChatPay.class);
        paymentMap.put("unionpay", UnionPay.class);
    }
    
    public static Payment createPayment(String type) {
        try {
            Class<? extends Payment> paymentClass = paymentMap.get(type.toLowerCase());
            if (paymentClass == null) {
                throw new IllegalArgumentException("不支持的支付类型:" + type);
            }
            // 通过反射创建支付对象
            return paymentClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("创建支付对象失败", e);
        }
    }
    
    // 注册新的支付方式
    public static void registerPayment(String type, Class<? extends Payment> paymentClass) {
        paymentMap.put(type.toLowerCase(), paymentClass);
    }
}

使用反射机制后,可以轻松添加新的支付方式而无需修改工厂类:

import org.springframework.context.annotation.Configuration;

// 添加新的支付方式:PayPal
public class PayPal implements Payment {
    @Override
    public void init() {
        System.out.println("初始化PayPal支付环境...");
    }

    @Override
    public boolean pay(double amount) {
        System.out.println("PayPal支付 " + amount + " 元");
        return true;
    }
}

// 在应用启动时注册
ReflectPaymentFactory.registerPayment("paypal", PayPal.class);

// 使用
Payment paypal = ReflectPaymentFactory.createPayment("paypal");
paypal.init();
paypal.pay(500.0);

在Java 8+的函数式编程中,工厂模式也有了新的实现方式:

// 使用Lambda表达式和函数式接口
public class FunctionalPaymentFactory {
    private static Map<String, Supplier<Payment>> paymentMap = new HashMap<>();
    
    static {
        paymentMap.put("alipay", AliPay::new);
        paymentMap.put("wechat", WeChatPay::new);
        paymentMap.put("unionpay", UnionPay::new);
    }
    
    public static Payment createPayment(String type) {
        Supplier<Payment> supplier = paymentMap.get(type.toLowerCase());
        if (supplier == null) {
            throw new IllegalArgumentException("不支持的支付类型:" + type);
        }
        return supplier.get();
    }
    
    // 注册新的支付方式
    public static void registerPayment(String type, Supplier<Payment> supplier) {
        paymentMap.put(type.toLowerCase(), supplier);
    }
}

随着Spring等框架的普及,我们也可以借助依赖注入容器实现更加优雅的工厂模式:

@Configuration
public class PaymentConfig {
    @Bean
    public Map<String, Payment> paymentMap() {
        Map<String, Payment> map = new HashMap<>();
        map.put("alipay", new AliPay());
        map.put("wechat", new WeChatPay());
        map.put("unionpay", new UnionPay());
        return map;
    }
    
    @Bean
    public PaymentFactory paymentFactory(Map<String, Payment> paymentMap) {
        return new SpringPaymentFactory(paymentMap);
    }
}

public class SpringPaymentFactory {
    private final Map<String, Payment> paymentMap;
    
    public SpringPaymentFactory(Map<String, Payment> paymentMap) {
        this.paymentMap = paymentMap;
    }
    
    public Payment getPayment(String type) {
        Payment payment = paymentMap.get(type.toLowerCase());
        if (payment == null) {
            throw new IllegalArgumentException("不支持的支付类型:" + type);
        }
        return payment;
    }
}

五、总结与展望

工厂模式的核心要点

  1. 关注点分离:将对象的创建与使用分开,让客户端专注于业务逻辑
  2. 封装变化:封装了对象创建的逻辑,使系统更容易扩展
  3. 开闭原则:尤其是工厂方法模式,很好地体现了开闭原则
  4. 依赖倒置原则:客户端依赖抽象接口,而非具体实现类

在所有项目中合适的永远才是最好的,所以我们在使用工厂模式时,需要遵循以下几点:

  1. 选择合适的工厂模式:根据实际需求选择简单工厂、工厂方法或抽象工厂
  2. 避免过度设计:有时候简单工厂或静态工厂方法就够用了
  3. 与其他模式结合:工厂模式常与单例模式、策略模式等结合使用
  4. 注意性能问题:反射虽然灵活,但性能较低,需谨慎使用

参考资料

  1. 《重学Java设计模式》
  2. 《芋道源码》

下一篇预告

在下一篇中,我们将探讨另一个重要的设计模式——策略模式。策略模式与工厂模式有很多相似之处,但解决的问题不同。我们将学习如何在实际开发中灵活运用策略模式,使代码更具弹性和可维护性。

最后,希望朋友们看完这篇文章会有有一点点启发与借鉴意义,我会继续努力,输出更多我对设计模式的理解。