构建 Laravel 应用程序的开发者正被敦促弃用控制器中的 new 关键字,转而使用工厂方法(Factory Method)模式。通过将对象创建移至专门的工厂中,代码变得更易于扩展、测试和维护——随着项目规模超出少数几个端点,这些优势将变得至关重要。
为什么 new 关键字是一个隐藏的隐患
当控制器包含如下代码行时:
$processor = new StripePaymentProcessor($config);
该控制器现在与 StripePaymentProcessor 类紧密耦合。每个需要支付处理器的位置都会重复该行代码,将构造逻辑分散在整个代码库中。如果处理器稍后需要日志记录器(logger)、缓存管理器(cache manager)或不同的配置格式,则必须更新每一个此类位置。其结果是产生脆弱的代码,难以应对变化,且在单元测试中难以进行 mock(模拟)。
工厂方法模式的介入
工厂方法模式通过一个返回给定接口实例的方法来取代直接实例化。控制器依赖于接口,而工厂类则知道要构建哪个具体类以及如何组装其依赖项。所有的创建逻辑都集中在一个地方,因此添加新依赖或更换实现只需修改工厂即可。
核心优势
- 解耦 – 控制器针对抽象而非具体类进行工作。
- 集中式构造 – 更改构建过程只需编辑单个文件。
- 可测试性 – 工厂可以被 stub(桩)或替换为 mock(模拟),从而实现隔离的控制器测试。
- 开闭原则 – 无需改动现有的控制器代码即可添加新功能(例如新的支付网关)。
咖啡店类比
想象一下,一名咖啡师必须为每一份订单手动磨豆、蒸奶和倒水,并使用一长串的 if-else 语句。如果拿铁的配方变了,每位咖啡师都必须重新学习步骤。而一台接收订单类型并在内部处理准备工作的咖啡机则解决了这个问题:咖啡师只需告诉机器需要什么,机器就会封装所有步骤。现在更新配方意味着调整机器,而不是调整每一位咖啡师。
Laravel 已经在依赖工厂
Laravel 自身的内核组件展示了该模式的实际应用:
- 数据库 –
ConnectionFactory决定是构建 MySQL 还是 PostgreSQL 连接。 - 队列 –
QueueManager为 Redis、SQS 或其他后端创建驱动程序。 - 文件系统 –
FilesystemManager生成本地或 S3 磁盘实例。 - 邮件 –
MailManager解析 SMTP、Mailgun 或其他邮件驱动程序。
如果框架在这些关键服务上信任工厂,那么自定义代码也应效仿。
何时引入工厂
在以下情况下使用工厂:
- 对象创建涉及多个配置步骤或外部服务。
- 存在多个可互换的实现(不同的支付网关、存储提供商等)。
- 预期的实现列表将会增长。
在以下情况下避免使用工厂:
- 构造只是一个单一、不可变的
new调用,且没有额外的设置。 - 始终只会使用一种实现,使得额外的抽象成为不必要的开销。
快速演练:重构支付控制器
- 定义接口 – 包含
process(array $data)方法的PaymentProcessorInterface。 - 实现具体类 –
StripePaymentProcessor、PayPalPaymentProcessor,每个类都实现该接口。 - 创建工厂 – 包含
make(string $driver): PaymentProcessorInterface方法的PaymentProcessorFactory。在内部,使用switch或映射(map)来返回适当的类,并从 Laravel 的服务容器中注入任何所需的依赖服务。 - 注入工厂 – 在控制器的构造函数中,使用
PaymentProcessorFactory进行类型提示(type-hint)。Laravel 会自动解析它。 - 使用工厂 –
$processor = $this->processorFactory->make('stripe'); $processor->process($request->all());
现在,控制器再也不会提到 new 或具体的处理器。添加新的网关只需创建一个类并扩展工厂的映射——无需改动控制器。
潜在的缺点及缓解方法
对工厂模式的主要批评是增加了间接性,对于简单的对象来说,这可能会显得过于冗长。对简单的服务进行过度设计可能会在没有实际收益的情况下膨胀代码库。关键在于评估构建的复杂度:如果一个对象只是一个没有依赖关系的纯数据持有者,那么直接使用 new 可能是可以接受的。此外,工厂可能会变成无关逻辑的堆放地。让工厂专注于对象创建,并将配置委托给专门的服务提供者(service providers),可以保持代码的清晰度。
下一步需要注意的事项
- 服务容器绑定 – 如果你在服务提供者中将接口绑定到工厂类,Laravel 的容器就可以自动解析工厂。
- 自动发现 – 某些扩展包会暴露它们自己的工厂;请注意命名冲突。
- 测试策略 – 在对控制器进行单元测试时,请用返回桩处理器(stubbed processor)的 mock 对象替换工厂,以确保测试保持快速且隔离。
总结
用放置得当的工厂取代散乱的 new 调用,可以使构建过程集中化,减少耦合,并使自定义代码与 Laravel 自身的设计理念保持一致。对于预见到会有增长需求的团队——例如新增支付提供商、存储后端或任何插件式组件——工厂方法(Factory Method)模式是一种低成本的投资,它将在灵活性和信心方面带来回报。未来的你,以及任何继承这段代码的人,都会感谢你。
