《PHP工厂模式深度解剖:从新手到架构师的进阶之路(附实战代码)》**

目录导读
- 为什么你需要工厂模式?——从一段“糟糕”的代码说起
- 工厂模式家族图谱:简单工厂、工厂方法、抽象工厂
- 核心实战:用PHP实现三种工厂模式(附完整可运行代码)
- 工厂模式在Laravel框架中的真实应用(服务容器与门面)
- 高频面试题问答:工厂模式 vs 单例模式 vs 依赖注入
- 何时不该用工厂模式?——设计过度陷阱警示
- 工厂模式的“道”与“术”
为什么你需要工厂模式?——从一段“糟糕”的代码说起
假设你在开发一个电商系统,需要处理多种支付方式(支付宝、微信、PayPal),初级程序员可能会这样写:
// 订单处理逻辑
if ($payType == 'alipay') {
$pay = new Alipay();
} elseif ($payType == 'wechat') {
$pay = new WechatPay();
} elseif ($payType == 'paypal') {
$pay = new Paypal();
}
$pay->pay($amount);
这段代码的致命缺陷:
- 违背开闭原则:每增加一种支付方式,必须修改这段业务代码。
- 耦合度高:业务层直接依赖具体支付类,无法独立测试。
- 重复劳动:如果支付类还有参数配置(如密钥),每次实例化都要重复初始化。
工厂模式的核心思想:将对象的创建逻辑封装到一个独立的类(工厂)中,业务代码只依赖工厂接口,从而将“使用对象”与“创建对象”彻底解耦。
工厂模式家族图谱:简单工厂、工厂方法、抽象工厂
工厂模式并非单一模式,而是一个家族,三者关系层层递进:
| 模式类型 | 核心特点 | 适用场景 | 典型代码结构 |
|---|---|---|---|
| 简单工厂 | 一个工厂类,根据参数返回不同产品 | 产品种类少,且产品类无等级结构 | PaymentFactory::create($type) |
| 工厂方法 | 每个产品对应一个具体工厂,通过继承工厂接口实现 | 需要推迟产品创建到子类 | abstract class PaymentFactory { abstract create(); } |
| 抽象工厂 | 创建一系列相关或相互依赖的产品族 | 产品族概念明确(如“小米生态链产品”) | abstract class DeviceFactory { createPhone(); createWatch(); } |
关键区别:
- 简单工厂是“一夫多妻制”——上帝工厂决定一切。
- 工厂方法是“多子多福”——每个工厂只生一个孩子。
- 抽象工厂是“家族联姻”——一个工厂必须生出一整套产品。
核心实战:用PHP实现三种工厂模式(附完整可运行代码)
场景:物流系统需要发货,支持不同的快递公司(顺丰、圆通、中通)。
① 简单工厂(Simple Factory)
interface Shipping {
public function send($package);
}
class SFExpress implements Shipping {
public function send($package) {
echo "顺丰已揽收:{$package}\n";
}
}
class YTOExpress implements Shipping {
public function send($package) {
echo "圆通已揽收:{$package}\n";
}
}
class ShippingFactory {
public static function create($company) {
switch ($company) {
case 'sf': return new SFExpress();
case 'yt': return new YTOExpress();
default: throw new Exception("未知快递公司");
}
}
}
// 客户端使用
$shipping = ShippingFactory::create('sf');
$shipping->send('包裹#001');
② 工厂方法(Factory Method)
abstract class ShippingFactory {
abstract public function createShipping(): Shipping;
public function ship($package) {
$shipping = $this->createShipping();
$shipping->send($package);
}
}
class SFFactory extends ShippingFactory {
public function createShipping(): Shipping {
return new SFExpress();
}
}
class YTOFactory extends ShippingFactory {
public function createShipping(): Shipping {
return new YTOExpress();
}
}
// 客户端
$factory = new SFFactory();
$factory->ship('包裹#002');
③ 抽象工厂(Abstract Factory)
interface 智能硬件Factory {
public function createPhone(): Phone;
public function createRouter(): Router;
}
class XiaomiFactory implements 智能硬件Factory {
public function createPhone(): Phone {
return new MiPhone();
}
public function createRouter(): Router {
return new MiRouter();
}
}
class HuaweiFactory implements 智能硬件Factory {
public function createPhone(): Phone {
return new HuaweiPhone();
}
public function createRouter(): Router {
return new HuaweiRouter();
}
}
// 客户端可通过工厂一次性获得全套产品
工厂模式在Laravel框架中的真实应用(服务容器与门面)
PHP最热门的Laravel框架处处渗透着工厂模式:
- 服务容器解析:
app(Alipay::class)本质是通过反射调用工厂方法进行自动依赖注入。 - 门面(Facade):
Cache::get()的底层是CacheManager工厂,根据配置动态创建RedisConnector或DatabaseConnector。 - 队列驱动:
Queue::connection('sqs')内部通过工厂选择SQS或Redis驱动。
真实生产代码:
Laravel中的 DatabaseManager 就是一个标准工厂——根据配置动态生成 MySQL、PostgreSQL 或 SQLite 连接实例,且支持连接池复用。
高频面试题问答:工厂模式 vs 单例模式 vs 依赖注入
Q1:工厂模式和单例模式能否结合?
可以,例如Laravel的容器就是“单例工厂”——它创建的对象默认是单例(共享),但也可以通过 bind 方法强制每次生产新实例。
Q2:工厂模式和依赖注入(DI)有什么区别?
- 工厂模式是“你让我建,我建好给谁”。
- 依赖注入是“你不必建,外面有人给你送”。
两者常配合:容器(DI容器)内部通常使用工厂模式来创建实例。
Q3:抽象工厂和工厂方法都返回产品,怎么选?
如果产品之间存在强关联性(小米手机必须配小米路由”),使用抽象工厂;如果产品是独立个体,选择工厂方法,判断标准:产品族 vs 产品层级。
何时不该用工厂模式?——设计过度陷阱警示
工厂模式并非万能银弹,以下情况应果断放弃:
- 产品只有两个,且几乎不变:直接
new反而清晰。 - 实例化过程极度简单(如
new stdClass),工厂只会增加一个无用层级。 - 框架已内置容器:在Laravel中,直接通过容器调用
app()即可,无需手写工厂类。 - 测试驱动场景:如果单纯为了mock,优先使用依赖注入,而不是工厂模式。
行业实证:GitHub 上大量 PHP 老旧代码,过度工厂化导致调试困难,知名 PHPStorm 插件 PHP Mess Detector 甚至会检测“滥用工厂”给出警告。
工厂模式的“道”与“术”
“术”层面:
掌握三种工厂的UML类图与分工明确,区分“想要一个产品”还是“想要一整套产品”。
“道”层面:
工厂模式的本质是封装变化——把“将来可能扩展的创建逻辑”集中管理,它让软件系统更有弹性,让每一次业务扩展只是新增一个类,而不必修改旧代码(满足开闭原则)。
最后一句忠告:真实项目中,80%的工厂模式使用“简单工厂”就够用;只有系统规模大到产品族爆炸时,才建议上抽象工厂,设计模式是解决问题的工具,不是用来炫耀的证书。
(全文完)