【问题标题】:Why use service container instead of new class为什么使用服务容器而不是新类
【发布时间】:2021-06-24 18:03:21
【问题描述】:

在什么场景下我们应该使用服务容器而不是直接调用一个新的类? 我从 Laravel 官方文档中读到它主要用于管理类依赖和执行依赖注入,但是我不确定我是否理解正确。

例如工资单应用程序,我们将有一个工资单类、员工类、休假类、时间表类。 因此,要处理每月工资单,工资单类将取决于

  • 员工类 (getBasicSalary)
  • 请假类 (getUnpaidLeaveCount)
  • 时间表类 (getLateHoursCount)

下面是我平时写的(没有任何设计模式)

Class PayrollProcessController 
{
    public function index(){
         $payrollObj = new Payroll();
         $payroll->setPayrollMonth('202106');
         $payroll->process();
    }
}

Class Payroll 
{
    private $payrollMonth;

    public function process()
    {
        // loop employee from database
        foreach ($employees as $employee_id){
             $salary = $this->calculateNetSalary($employee_id); 
             // save into db
        }
    }

    public function calculateNetSalary($employee_id)
    {
         $employee = Employee::find($employee_id);
         $basicSalary = $employee->getBasicSalary();
         $basicSalaryPerDay = $basicSalary / date('t');         
         $basicSalaryPerHour = $basicSalaryPerDay / 8;
         $leaveTakenDay = Leave::getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
         $leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
         $timesheetLateHours = Timesheet::getLateHoursCount($employee->getId(), $this->payrollMonth);
         $lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
         $netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
         return $netSalary;
    }
}

如果应用服务容器概念,

Class PayrollProcessController 
{
    public function index(Payroll $payroll){  
          $payroll->setPayrollMonth('202106');        
          $payroll->process();
    }
}

Class Payroll 
{
    public function process()
    {
        // loop employee from database
        foreach ($employees as $employee_id){

            $employee = app(Employee::class);
            $employee = $employee->find($employee_id);
            $leave = app(Leave::class);
            $timesheet = app(Timesheet::class);

            $salary = $this->calculateNetSalary($employee, $leave, $timesheet); 
             // save into db
        }
    }

    public function calculateNetSalary(Employee $employee, Leave $leave, Timesheet $timesheet)
    {
         $basicSalary = $employee->getBasicSalary();
         $basicSalaryPerDay = $basicSalary / date('t');         
         $basicSalaryPerHour = $basicSalaryPerDay / 8;
         $leaveTakenDay = $leave->getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
         $leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
         $timesheetLateHours = $timesheet->getLateHoursCount($employee->getId(), $this->payrollMonth);
         $lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
         $netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
         return $netSalary;
    }
}

问题:

  1. 上述概念正确吗?我可以看到服务容器允许我将工资单、员工、时间表、休假类移动到 laravel 包中,以便我可以安装在另一个 laravel 应用程序中(主要好处是我可以重用代码并覆盖类如果需要,在服务提供者中指定类路径)

  2. 我再次查看了服务容器文档 https://laravel.com/docs/8.x/container#when-to-use-the-container,是这样写的:

    首先,如果您编写一个实现接口的类,并且希望在路由或类构造函数中对该接口进行类型提示,则必须告诉容器如何解析该接口。其次,如果您正在编写一个 Laravel 包并计划与其他 Laravel 开发人员共享,您可能需要将包的服务绑定到容器中。

    所以我可以说除非我们将工资单模块发布为 laravel 包供其他人使用或编写单元测试,否则使用服务容器没有意义?

  3. 如果我们看一下 opencart 的注册类,是不是类似于 Laravel 的服务容器? https://github.com/opencart/opencart/blob/e22ddfb060752cd8134abbfb104202172ab45c86/upload/system/framework.php#L9

  4. 以opencart为例,Language类是单例设计模式,而registry是容器设计模式?

    $language = new \Opencart\System\Library\Language($config->get('language_code'));
    $language->addPath(DIR_LANGUAGE);
    $language->load($config->get('language_code'));
    $registry->set('language', $language);
    

【问题讨论】:

    标签: php laravel design-patterns containers


    【解决方案1】:

    依赖注入是通过 Laravel IoC 容器(service container)实现的,目的是分离对象的创建和使用。所以我认为拥有app(Employee::class); 并不比new EmployeeEmployee::create() 好。 IoC 不仅仅是向容器注册对象并在需要时将它们拉出。

    一个实际的例子可能是你有一个传输SMS消息的类。

    class Sms
    {
        private $smsProvider;
    
        public function send()
        {
            $this->smsProvider->send();
        }
    }
    

    所以你的SmsSender 类需要smsProvider 才能运行。与其在Sms 类中创建SMS 提供程序的new 实例,不如将提供程序注入到类中(通常是此类示例中的构造函数)并以这种方式使用它。如果您想让它更加灵活,您可以为SMS 提供者定义一个接口,然后允许服务容器从您的 IoC 映射中注入正确的具体类。

    class Sms
    {
        private $smsProvider;
    
        public __construct(ISmsProvider $smsProvider)
        {
            $this->smsProvider = $smsProvider;
        }
    
        public function send()
        {
            $this->smsProvider->send();
        }
    }
    
    class SmsTwillio implements ISmsProvider
    {
    }
    
    class SmsNexmo implements ISmsProvider
    {
    }
    

    然后,您将在服务容器中定义映射,以将对 ISmsProvider 的引用绑定到具体实现,例如 SmsTwillioSmsNexmo

    使用您的示例,您可以定义一个PayrollService

    class PayrollService
    {
        public function process($employees, $start, $end = null)
        {
            foreach ($employees as $employee) {
                $salary = $this->calculateNetSalary($employee);
            }
        }
    
        private function calculateNetSalary(Employee $employee)
        {
        }
    }
    
    class ProcessPayrollController extends ProcessPayrollController
    {
        private $payrollService;
    
        public __constructor(PayrollService $PayrollService)
        {
            $this->payrollService = $payrollService;
        }
    
        public function __invoke()
        {
            $result = $this->payrollService->process(Employee::all(), '2021-06-01');
        }
    }
    

    我认为您的 Payroll 课程不需要 LeaveTimesheet。我会在返回该数据的 Employee 类上定义 functionsscopes

    $employee->daysUnpaidLeave('2021-06-01');
    
    $employee->hoursLate('2021-06-01');
    

    其中日期是要从今天开始计算的start 日期,或者提供可选的end 日期作为第二个参数。

    您可以更深入地了解兔子洞并查看其他 creational design patterns 以在运行时创建对象,但是,设计模式可能会很快被滥用并增加而不是降低复杂性,因此请保持简单。

    OpenCart 中的Registry 是类似的主体。在该示例中,$registry->get('language'); 将始终返回相同的 Language 实例,所以是的,它将是一个单例。

    您的 Payroll 可能是一个单例,因为您不需要它的多个实例,因为它只是处理您提供给它的数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-08
      • 1970-01-01
      • 2012-01-23
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多