用静态方法封装对象创建逻辑,返回统一接口类型,避免散落的new、解耦调用方与具体类,适用于构造依赖外部配置或需统一初始化的场景。

PHP里怎么写一个能用的工厂类
直接给结论:用静态方法封装对象创建逻辑,别让调用方关心具体类名和构造细节。核心不是“设计模式”这个词,而是解决「new 太多、类型分散、改起来牵一发而动全身」的问题。
常见错误现象:new User()、new Admin()、new Guest() 在控制器里到处出现;加个新角色就得翻遍所有 new 地方;单元测试时没法轻松替换成 Mock 对象。
- 工厂类本身不继承任何东西,也不实现接口——它只是个纯工具类
- 返回类型统一用接口或抽象类(比如
UserInterface),而不是具体类名 - 避免在工厂里做复杂业务判断,比如「根据 IP 地址决定返回哪个用户」——那是策略模式的事,工厂只管“造出来”,不管“为什么造”
- 如果参数差异大(比如有的要传
$id,有的要传$token),优先拆成多个静态方法,比如createFromId()和createFromToken(),别硬塞进一个create()
什么时候该用工厂,而不是 new 或 DI 容器
不是所有对象创建都适合工厂。关键看「创建逻辑是否稳定」和「调用方是否需要解耦」。
使用场景:
- 对象构造依赖外部配置(比如不同环境用不同缓存驱动,但代码里不能写死
new RedisCache()) - 类名可能变化(比如从
JsonLogger换成SentryLogger,但日志接口不变) - 需要统一初始化行为(比如每个
Connection实例都必须调用connect())
别用工厂的情况:
- 对象很简单,没依赖、没配置、没状态(比如
new DateTime()) - 项目已用 DI 容器(如 Laravel 的
app()->make()或 Symfony 的$container->get()),再写一层工厂纯属重复劳动 - 工厂方法里开始做 if-else 类型路由(比如
if ($type === 'mysql') { return new MysqlAdapter(); })——这其实是简单工厂的退化,该上抽象工厂或策略了
静态工厂 vs 抽象工厂:PHP 里怎么选
PHP 没有接口方法的重载,也没办法像 Java 那样靠泛型约束类型,所以「抽象工厂」在 PHP 里容易写得笨重又难测。大多数时候,静态工厂够用,且更直觉。
静态工厂示例:
class UserRepositoryFactory
{
public static function create(string $driver): UserRepositoryInterface
{
return match ($driver) {
'mysql' => new MysqlUserRepository(),
'redis' => new RedisUserRepository(),
default => throw new InvalidArgumentException("Unknown driver: {$driver}"),
};
}
}
抽象工厂适用场景极少,仅当你要「批量创建一组相关对象」时才考虑,比如:
- 一套支付模块:同时需要
PaymentGateway、RefundProcessor、NotificationService - 这些类之间有强约定(比如都基于同一套签名密钥),不能随意混搭
- 而且你确定未来会切换整套实现(比如从支付宝换到微信支付)
否则,抽象工厂只会让代码变深、测试变难、IDE 自动补全失灵。
工厂类里最容易被忽略的三个点
不是语法问题,而是协作和维护时真实踩过的坑。
- 工厂方法没加
final或文档说明,结果被子类重写,导致下游调用行为突变 - 工厂返回的对象没有明确生命周期管理提示(比如是否单例?是否可复用?),调用方反复 new 工厂又反复 get 实例,内存悄悄涨
- 工厂内部用了全局函数(如
getenv()、date())或静态调用(如Config::get()),导致无法在测试中可靠 stub —— 正确做法是把依赖作为参数传入,或通过构造函数注入(哪怕工厂本身是静态的,也可以接受可选的依赖实例)
工厂不是银弹,它只是把「谁来 new」和「new 谁」这两件事分开。分得清楚,代码才好动;分得含糊,反而多一层迷雾。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











