3、工厂方法模式:简单工厂 vs 工厂方法、PHP实现、在支付网关对接中的应用、开闭原则的体现

3.1 从简单工厂说起——我踩过的第一个坑

先聊聊简单工厂。说白了,它就是一个“调度中心”。你告诉它要什么,它给你造什么。

我记得刚入行那会儿,写支付对接就用的简单工厂。代码大概长这样:

class PaymentFactory
{
    public static function create($type)
    {
        switch ($type) {
            case 'alipay':
                return new AlipayService();
            case 'wechat':
                return new WechatPayService();
            case 'unionpay':
                return new UnionPayService();
            default:
                throw new \InvalidArgumentException('不支持的支付方式');
        }
    }
}

看起来挺清爽,对吧?但问题很快就来了。

有一次,产品经理跑过来说:“老王,我们要接入银联云闪付。”我打开代码,找到 switch 语句,加了一个 case。改完测试通过,上线了。

过了一个月,又说:“再加个京东支付。”我又改 switch。再后来是“美团支付”、“抖音支付”……每次都要动这个工厂类。

你想想看,每次加一种支付方式,就要改一次 PaymentFactory。这违反了哪个原则?开闭原则——对扩展开放,对修改关闭。我每次都在修改,这哪叫关闭?

我曾经踩过的坑: 简单工厂的 switch 语句会随着业务膨胀变成“屎山”。我见过一个项目,工厂类里 30 多个 case,改一个地方影响全局,测试要回归所有支付渠道。那叫一个酸爽。

3.2 工厂方法模式——把“创建”这件事交给子类

工厂方法模式怎么解决这个问题?

核心思想就一句话:定义一个创建对象的接口,让子类决定实例化哪个类。

说白了,就是把 switch 拆掉,每个支付方式自己建一个工厂。这样加新支付方式,不用改老代码,只要写新类就行。

来看 PHP 实现:

// 1. 支付接口(产品)
interface PaymentInterface
{
    public function pay($order);
}

// 2. 具体支付服务
class AlipayService implements PaymentInterface
{
    public function pay($order)
    {
        echo "调用支付宝接口,订单:{$order}\n";
    }
}

class WechatPayService implements PaymentInterface
{
    public function pay($order)
    {
        echo "调用微信支付接口,订单:{$order}\n";
    }
}

// 3. 工厂接口(Creator)
interface PaymentFactoryInterface
{
    public function create(): PaymentInterface;
}

// 4. 具体工厂
class AlipayFactory implements PaymentFactoryInterface
{
    public function create(): PaymentInterface
    {
        return new AlipayService();
    }
}

class WechatPayFactory implements PaymentFactoryInterface
{
    public function create(): PaymentInterface
    {
        return new WechatPayService();
    }
}

// 5. 客户端使用
$factory = new AlipayFactory();
$payment = $factory->create();
$payment->pay('ORDER2024001');

嗯,这里要注意:代码量确实比简单工厂多了。但换来的是什么?扩展性

3.3 在支付网关对接中的实际应用

我在做支付网关项目时,对接了 10 多家支付渠道。如果用简单工厂,那个 switch 会写到让人崩溃。

用工厂方法后,结构变成了这样:

支付渠道 产品类 工厂类
支付宝 AlipayService AlipayFactory
微信支付 WechatPayService WechatPayFactory
银联 UnionPayService UnionPayFactory
京东支付 JdPayService JdPayFactory

每次新增渠道,我只需要:

  1. 写一个 XxxPayService 实现 PaymentInterface
  2. 写一个 XxxPayFactory 实现 PaymentFactoryInterface
  3. 在配置里注册一下

老代码一行都不用动。测试也只需要测新渠道,老渠道不受影响。

个人经验: 我习惯把工厂实例的获取也抽象一层,用配置驱动。比如从数据库或配置文件读取当前启用的支付方式,然后动态实例化对应的工厂。这样连代码都不用改,改配置就行。

3.4 开闭原则的体现——为什么工厂方法更“干净”

开闭原则说:对扩展开放,对修改关闭

简单工厂:加新支付方式 → 修改 PaymentFactoryswitch → 违反开闭原则。

工厂方法:加新支付方式 → 新增 XxxFactoryXxxService → 符合开闭原则。

你想想看,哪个更安全?肯定是后者。因为修改老代码的风险,永远比写新代码大。

我曾经接手过一个项目,简单工厂里 20 多个 case,每次上线都提心吊胆。生怕改了一个 case 影响到另一个。后来重构成了工厂方法,世界清净了。

避坑指南: 工厂方法也不是银弹。如果你的支付方式只有两三种,而且几乎不变,简单工厂完全够用。别为了设计模式而设计模式,过度设计比没有设计更可怕。

3.5 简单工厂 vs 工厂方法——一张表说清楚

对比维度 简单工厂 工厂方法
代码量 少,集中管理 多,每个产品对应一个工厂
扩展性 差,改 switch 好,加新类即可
开闭原则 违反 符合
适用场景 产品种类少、变化少 产品种类多、频繁扩展
维护成本 低(初期)→ 高(后期) 高(初期)→ 低(后期)

我个人习惯:新项目如果预判支付方式会超过 3 种,直接上工厂方法。如果只有 1-2 种,简单工厂先顶着,等需要扩展时再重构。别一开始就搞复杂了。

最后说一句:设计模式是工具,不是教条。理解背后的思想——开闭原则、单一职责——比记住代码模板重要得多。