返回

Laravel工厂模式: 依赖注入与自定义参数详解

php

工厂模式下注入服务依赖及自定义参数

在开发中,工厂模式常被用来创建对象,降低耦合,并且集中管理对象的创建逻辑。一种常见的场景是,需要创建的服务依赖于其他服务,同时还需要传递一些自定义的参数。问题在于,如何在 Laravel 框架下,优雅地解决服务依赖注入,同时又能灵活地传递自定义参数呢?本文将探讨几种常见的解决方案。

问题分析

以上面提供的 ActivityReportActivityReportCreator 代码为例。ActivityReport 需要 ActivityService 的实例来完成工作,这个 ActivityService 通常应该通过依赖注入 (DI) 来实现。但是 ActivityReportCreatorgenerate 方法直接使用了 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();
    }
}

操作步骤:

  1. ActivityReportCreatorgenerate 方法内的 new ActivityReport($customer->id); 修改为 app()->make(ActivityReport::class, ['customer' => $this->customer]);
  2. 确保ActivityReport类的构造函数接收Customer对象。
  3. 使用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);
    }
}

操作步骤:

  1. ActivityReportCreator 也注册为依赖,使得它可以注入需要的ActivityService
  2. ActivityReportCreator 中增加 make 方法用于实际的实例生成。
  3. 在需要使用报告的地方实例化 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方法进行创建。
这种方案可以复用和拓展更通用化的工厂模型。