Menu
Coddy logo textTech

依存性注入

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 57/91。

前のレッスンでは、コンポジションによって CarEngine オブジェクトを含められることを学びました。しかし、クラス内で依存関係を作成することには問題があります。コードが柔軟性を失い、テストしにくくなるのです。依存性注入は、依存関係を内部で作成するのではなく外部から渡すことで、この問題を解決します。

次の2つのアプローチを比較してください:

<?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); // 外部から注入

2つ目のアプローチは依存性注入です。Carは自分自身のengineを作成せず、engineを受け取ります。この単純な変更には大きな利点があります。異なる種類のengineを渡したり、テストのために実装を入れ替えたりでき、classはより柔軟になります。

さらに柔軟性を高めるには、具象クラスではなくインターフェースを注入します。

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

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

現在、CarEngineInterfaceを実装するあらゆるclass(ガスエンジン、電気モーター、またはテスト用のモック)と連携できます。この疎結合により、dependency injectionは保守性とテスト容易性に優れたPHPアプリケーションの作成に不可欠です。

challenge icon

チャレンジ

簡単

dependency injection の力を示す notification system を構築しましょう。メッセージの送信方法をハードコードする代わりに、delivery method を簡単に入れ替えられる柔軟な system を作成します。

コードを4つのファイルに分けて整理します。

  • MessageSenderInterface.php: MessageSenderInterface という名前の interface を Define し、単一の method send(string $recipient, string $message): string を持たせます。この interface は、あらゆる message sender が従うべき contract を確立します。
  • EmailSender.php: MessageSenderInterface を implements する EmailSender class を Create します。interface file を含めます。send() method は "Email to [recipient]: [message]" を返す必要があります。
  • NotificationService.php: constructor を通じて dependency を受け取る NotificationService class を Create します。interface file を含めます。この class は次のようにします。
    • constructor promotion を使用して、constructor で MessageSenderInterface を Accept する
    • 注入された sender に delegates し、その結果を返す notify(string $recipient, string $message) method を have する
    NotificationService が concrete class ではなく interface に依存していることに注目してください。つまり、service を変更せずに、interface を implements する任意の sender を Inject できます。
  • main.php: EmailSender と NotificationService の file を含めます。recipient と message の2つの入力を受け取ります。EmailSender を Create して NotificationService に Inject し、続いて notify() を Call して結果を print します。

この pattern によって、NotificationService は柔軟な状態に保たれます。email、SMS、またはまだ構築していないものなど、どのような sender を Inject しても動作します。service 自体が dependency を Create するのではなく、outside から受け取ります。

自分で試してみよう

<?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オンラインコンパイラ