Menu
CoddyTech

依赖注入

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 57 课,共 91 课。

在上一课中,你了解了组合如何让 Car 包含一个 Engine 对象。但在类内部创建依赖会带来一个问题:这会让代码变得僵化,难以测试。依赖注入通过从外部传入依赖,而不是在内部创建依赖,解决了这个问题。

比较这两种方法:

<?php
// 未使用 DI,依赖在内部创建
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // 紧密耦合
    }
}

// 使用 DI,依赖从外部传入
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // 从外部注入

第二种方法是依赖注入。Car 不会创建自己的引擎,而是接收一个引擎。这个简单的改变有很多好处:你可以传入不同类型的引擎,为测试替换实现,让这个类变得更加灵活。

为了获得更大的灵活性,可以注入接口,而不是具体类:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

现在,Car 可以与任何实现了 EngineInterface 的类配合使用,例如汽油发动机、电动机,或用于测试的模拟对象。这种解耦正是依赖注入成为编写易于维护、易于测试的 PHP 应用程序的基础的原因。

challenge icon

挑战

简单

让我们构建一个通知系统,展示依赖注入的强大之处。你不会把消息发送方式写死,而是创建一个灵活的系统,可以轻松更换消息的发送方式。

你会将代码分成 4 个文件:

  • MessageSenderInterface.php:定义一个名为 MessageSenderInterface 的接口,其中只有一个方法 send(string $recipient, string $message): string。这个接口规定了所有消息发送器都必须遵守的约定。
  • EmailSender.php:创建一个实现 MessageSenderInterface 的 EmailSender 类。引入接口文件。send() 方法应返回 "Email to [recipient]: [message]"。
  • NotificationService.php:创建一个通过构造函数接收依赖项的 NotificationService 类。引入接口文件。这个类应该:
    • 在构造函数中使用构造函数属性提升来接收一个 MessageSenderInterface
    • 包含一个方法 notify(string $recipient, string $message),该方法会调用注入的发送器,并返回其结果
    注意,NotificationService 依赖的是接口,而不是具体的类。这意味着你可以注入任何实现该接口的发送器,而无需更改这个服务。
  • main.php:引入 EmailSender 和 NotificationService 文件。你会收到两个输入:收件人和消息。创建一个 EmailSender,将它注入 NotificationService,然后调用 notify() 并输出结果。

这种模式让你的 NotificationService 保持灵活。无论你注入的是电子邮件、短信发送器,还是尚未构建的其他发送器,它都能正常工作。这个服务不会自行创建依赖项,而是从外部接收它们。

亲手试试

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// 读取输入
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO:创建一个 EmailSender 实例
// TODO:将它注入 NotificationService
// TODO:调用 notify() 并打印结果

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器