Laravel工厂模式: 依赖注入与自定义参数详解
2025-01-25 13:39:09
工厂模式下注入服务依赖及自定义参数
在开发中,工厂模式常被用来创建对象,降低耦合,并且集中管理对象的创建逻辑。一种常见的场景是,需要创建的服务依赖于其他服务,同时还需要传递一些自定义的参数。问题在于,如何在 Laravel 框架下,优雅地解决服务依赖注入,同时又能灵活地传递自定义参数呢?本文将探讨几种常见的解决方案。
问题分析
以上面提供的 ActivityReport
和 ActivityReportCreator
代码为例。ActivityReport
需要 ActivityService
的实例来完成工作,这个 ActivityService
通常应该通过依赖注入 (DI) 来实现。但是 ActivityReportCreator
的 generate
方法直接使用了 new
实例化 ActivityReport
,导致服务依赖无法被 Laravel 的 IoC 容器自动解决。更进一步讲,原本应该在构造函数中注入的Customer
对象,反而传递到了方法的参数中,破坏了一致性,导致代码更加难懂。 这样一来,无法像控制器或其他服务那样使用依赖注入,破坏了Laravel 框架的使用体验。
解决方案
方案一:使用 app()->make()
方法
Laravel 提供了 app()
助手函数来获取容器中的实例。可以使用它来实例化 ActivityReport
, 并且允许传递构造函数参数,Laravel的DI容器会自动解析剩余的依赖。
代码示例:
class ActivityReportCreator extends ReportCreator
{
private $customer;
function __construct(Customer $customer)
{
$this->customer = $customer;
}
public function generate() {
$activity = app()->make(ActivityReport::class, ['customer' => $this->customer]);
return $activity->getData();
}
}
操作步骤:
- 将
ActivityReportCreator
的generate
方法内的new ActivityReport($customer->id);
修改为app()->make(ActivityReport::class, ['customer' => $this->customer]);
。 - 确保
ActivityReport
类的构造函数接收Customer
对象。 - 使用
ActivityReportCreator
的时候正常实例化即可。
$customer = Customer::find(1);
$report = new ActivityReportCreator($customer);
$reportData = $report->generate();
原理:
app()->make()
方法接收类的名称和一个数组,其中数组的键表示构造函数的参数名称,数组的值就是你需要传递的参数值。容器会查找依赖项,并且解决这些依赖,再传递传递过来的参数后实例化类。
方案二:使用工厂方法模式 (推荐)
另一种方法是通过工厂方法模式来实现。在这种方法中,我们让 ActivityReportCreator
作为真正的工厂,负责 ActivityReport
的实例化过程。这种方式更加的符合工厂模式的定义,解耦更彻底。
代码示例:
class ActivityReport
{
private $activityService;
private $customer;
public function __construct(Customer $customer, ActivityService $activityService)
{
$this->activityService = $activityService;
$this->customer = $customer;
}
public function getData()
{
// More Code Here.
return $this->activityService->getAll($this->customer->id);
}
}
class ActivityReportCreator extends ReportCreator
{
private $customer;
private $activityService;
public function __construct(Customer $customer, ActivityService $activityService)
{
$this->customer = $customer;
$this->activityService = $activityService;
}
public function generate() {
return $this->make($this->customer);
}
public function make(Customer $customer):ActivityReport
{
return new ActivityReport($customer,$this->activityService);
}
}
操作步骤:
- 将
ActivityReportCreator
也注册为依赖,使得它可以注入需要的ActivityService
。 - 在
ActivityReportCreator
中增加make
方法用于实际的实例生成。 - 在需要使用报告的地方实例化
ActivityReportCreator
,通过generate
方法生成ActivityReport
的实例。
原理:
ActivityReportCreator
类现在承担了工厂的角色,负责 ActivityReport
的实例化过程。通过依赖注入获取 ActivityService
的实例,再由 make
方法结合 Customer
对象一同注入,创建 ActivityReport
的实例。
安全提示
- 确保所有的服务依赖在 Laravel 容器中都已正确注册,避免出现服务未注册的异常。
- 避免在构造函数中进行复杂业务逻辑操作,应将其放在对应的方法中,如上面的
getData
方法。
通过这两种方法,可以在使用工厂模式的同时,仍然享受 Laravel 依赖注入带来的便利性,并能根据具体需要,灵活传递自定义参数,让你的代码结构更加清晰,也更加易于维护。
额外的想法
也可以在父类定义make
方法来构建。这样的方式会更清晰:
abstract class ReportCreator {
protected function make(Customer $customer, mixed ...$services) : mixed {
return new static($customer, ...$services);
}
}
class ActivityReport extends ReportCreator {
private $activityService;
private $customer;
public function __construct(Customer $customer, ActivityService $activityService)
{
$this->activityService = $activityService;
$this->customer = $customer;
}
public function getData(){
return $this->activityService->getAll($this->customer->id);
}
}
class ActivityReportCreator extends ReportCreator
{
private $activityService;
public function __construct(ActivityService $activityService)
{
$this->activityService = $activityService;
}
public function generate(Customer $customer)
{
return $this->make($customer, $this->activityService);
}
}
以上代码可以通过在父类ReportCreator
中使用可变参数方法定义一个抽象的工厂函数make
, 而所有的具体工厂都只需要负责把需要的依赖注入构造器。在使用具体产品的时候直接使用工厂的make方法进行创建。
这种方案可以复用和拓展更通用化的工厂模型。