依存性注入
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 57/91。
前のレッスンでは、コンポジションによって Car に Engine オブジェクトを含められることを学びました。しかし、クラス内で依存関係を作成することには問題があります。コードが柔軟性を失い、テストしにくくなるのです。依存性注入は、依存関係を内部で作成するのではなく外部から渡すことで、この問題を解決します。
次の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();
}
}
現在、CarはEngineInterfaceを実装するあらゆるclass(ガスエンジン、電気モーター、またはテスト用のモック)と連携できます。この疎結合により、dependency injectionは保守性とテスト容易性に優れたPHPアプリケーションの作成に不可欠です。
チャレンジ
簡単dependency injection の力を示す notification system を構築しましょう。メッセージの送信方法をハードコードする代わりに、delivery method を簡単に入れ替えられる柔軟な system を作成します。
コードを4つのファイルに分けて整理します。
MessageSenderInterface.php:MessageSenderInterfaceという名前の interface を Define し、単一の methodsend(string $recipient, string $message): stringを持たせます。この interface は、あらゆる message sender が従うべき contract を確立します。EmailSender.php:MessageSenderInterfaceを implements するEmailSenderclass を Create します。interface file を含めます。send()method は"Email to [recipient]: [message]"を返す必要があります。NotificationService.php: constructor を通じて dependency を受け取るNotificationServiceclass を 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 できます。- constructor promotion を使用して、constructor で
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() を呼び出して結果を出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: PHPオンラインコンパイラ