php工厂模式的核心是封装实例化逻辑,通过统一入口(如静态方法)根据明确标识创建符合接口的具体对象,避免业务代码中散落new操作,提升可维护性与可测试性。

PHP工厂模式的核心是把 new 操作集中到一个地方
工厂模式不是为了炫技,而是当对象创建逻辑变复杂、或需要根据条件切换具体类时,避免到处 scattered 的 new。直接在业务代码里写 new UserPayService() 或 new AlipayAdapter(),后续加微信支付、改构造参数、换依赖注入方式,就得满项目搜 new —— 这才是真实痛点。
所以工厂的本质是「封装实例化」:你告诉它要什么(比如 'alipay'),它返回一个符合接口的实例,不暴露具体类名和构造细节。
简单工厂:用一个静态方法分发 new 操作
适合小项目或初期快速解耦,不用设计一堆抽象类。常见错误是把判断逻辑硬编码进工厂,导致每次加新类型都要改工厂文件。
- 用
switch或match(PHP 8+)按字符串分发,别用 if-else 链套娃 - 所有返回对象必须实现统一接口(如
PaymentInterface),否则调用方无法安全调用->pay() - 构造参数尽量通过配置数组传入,而不是在工厂里写死
new AlipayAdapter('key', 'secret', true)
示例:
立即学习“PHP免费学习笔记(深入)”;
class PaymentFactory
{
public static function create(string $type, array $config = []): PaymentInterface
{
return match ($type) {
'alipay' => new AlipayAdapter($config['app_id'] ?? '', $config['private_key'] ?? ''),
'wechat' => new WechatPayAdapter($config['mch_id'] ?? ''),
default => throw new InvalidArgumentException("Unknown payment type: {$type}")
};
}
}
避免在工厂里做运行时依赖判断
有人会写“根据环境自动选支付方式”,比如 if (APP_ENV === 'test') return new MockPayment() —— 这会让工厂承担不该有的职责,也难测试。工厂只响应明确输入,环境适配该交给上层容器或配置驱动。
- 工厂函数参数必须是明确可枚举的标识(如字符串、枚举),不能是动态变量或全局状态
- 不要在工厂里调用
file_exists()、extension_loaded()做分支,那是启动引导或 DI 容器的事 - 如果真要支持运行时策略,把策略对象(如
PaymentStrategyInterface)作为参数传进去,而不是让工厂自己猜
工厂和依赖注入容器不是二选一
有人以为用了 Di\Container 就不用工厂了。其实容器负责生命周期和依赖解析,工厂负责“怎么造出这个具体实例”。比如容器可以绑定 PaymentInterface::class 到一个闭包工厂,而这个闭包内部仍可能调用你写的 PaymentFactory::create()。
- 容器里注册工厂闭包时,注意闭包捕获的变量作用域(比如
$config是从外部传入还是从全局取) - 工厂返回的对象如果带资源(如数据库连接、文件句柄),得确认是否被容器当成单例缓存 —— 多次调用
get(PaymentInterface::class)可能拿到同一个实例,这未必符合预期 - 调试时,
var_dump()工厂返回的对象,重点看get_class()和属性是否符合预期,别只看接口类型
真正容易被忽略的是:工厂返回的对象,其构造函数异常不会在工厂调用时立刻暴露,而是在第一次调用它的方法时才抛出 —— 所以单元测试里一定要触发实际行为,不能只测工厂返回了“某个对象”。











