绝大多数场景下不建议手写传统工厂类,应优先使用依赖注入容器;仅当需运行时动态选型或封装第三方SDK初始化时才考虑手写简单工厂,抽象工厂适用于多组件组合场景。

工厂模式在PHP里到底该不该手动写
绝大多数场景下,不建议手写传统工厂类。现代PHP项目依赖容器(如 PHP-DI、Laravel IoC、Symfony DependencyInjection)做对象创建和生命周期管理,比手工工厂更灵活、可测试、易维护。手写工厂容易变成“套壳 new”,没解决耦合,反而增加抽象层。
只有两种情况值得考虑手写简单工厂:
- 需要根据运行时参数(比如请求头、配置项)动态决定实例化哪个子类,且容器无法直接表达这种逻辑
- 封装第三方 SDK 初始化(如不同支付网关的
PayPalClient/AlipayClient),避免业务代码感知构造细节
用抽象工厂解决多套实现组合问题
当系统需要同时切换「数据库驱动 + 日志通道 + 缓存后端」这类配套组件时,抽象工厂比简单工厂更合适。它不是返回单个对象,而是一组协调工作的对象实例。
典型错误是把抽象工厂做成静态方法集合,导致无法注入依赖或 mock。正确做法是让工厂本身可被容器管理:
interface CacheFactory
{
public function createClient(): CacheClient;
}
class RedisCacheFactory implements CacheFactory
{
public function __construct(private string $host) {}
public function createClient(): CacheClient
{
return new RedisClient($this->host);
}
}
关键点:
- 工厂类本身应接受构造参数(如配置),而不是在方法里硬编码
new RedisClient('127.0.0.1') - 不要让工厂承担连接池、重试等职责——那是具体客户端的事
- 若某套实现需延迟初始化,用
callable或Proxy包裹,别在工厂里做 lazy logic
工厂模式和依赖注入容器怎么共存
工厂不是容器的替代品,而是补充。容器负责「声明依赖关系」,工厂负责「运行时决策逻辑」。常见误用是把所有条件分支塞进容器配置里,导致配置臃肿难读。
推荐做法:容器注入工厂实例,由工厂执行判断:
// 容器配置(PHP-DI)
return [
PaymentProcessor::class => \DI\factory([PaymentFactory::class, 'create']),
PaymentFactory::class => \DI\create(),
];
对应工厂:
class PaymentFactory
{
public function create(string $method): PaymentProcessor
{
return match ($method) {
'alipay' => new AlipayProcessor(...),
'wechat' => new WechatProcessor(...),
default => throw new InvalidArgumentException("Unknown method: $method"),
};
}
}
注意:
- 工厂方法参数必须是明确可推导的(如路由参数、表单字段),不能是「运行中计算出的状态值」,否则破坏可测性
- 避免在工厂里调用
$_SERVER、$_SESSION等超全局变量——改用传参或注入上下文对象 - 如果分支超过 4–5 种,考虑用策略模式+映射表替代硬编码
match
为什么工厂测试总失败
工厂类单元测试失败,90% 是因为隐式依赖没隔离。比如工厂内部直接 new 了 Logger,又没提供替换入口,导致测试时日志写入文件或触发网络请求。
修复要点:
- 所有被工厂创建的对象,其依赖必须能被构造函数传入,不能在工厂方法里 new 出来再 set
- 工厂自身依赖(如配置对象、HTTP client)要通过构造函数注入,而非
new Config() - 测试时用
Mockery::mock()或PHPUnit::createMock()替换工厂依赖,验证是否按预期调用了特定构造逻辑
最容易被忽略的是工厂的边界——它只管「选哪个类、传什么参数」,不负责验证参数合法性或处理异常。参数校验应该在调用方或领域服务里做,否则工厂会越来越重。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











