PHPで理解するDI

PHPで理解するDI

DIについて学習したことを記事にしました。例で用いるコードにはPHPを使用しています。

DI(Dependency Injection)について

クラス間の依存関係をなくすためのデザインパターンの1つです。 クラスの内部で他クラスのインスタンスを作成するのではなく、クラスの外部で他クラスのインスタンスを作成し、コンストラクタなどでインスタンスを渡す手法です。

依存が起こる例

例として、PHPを用いて通知を送信するクラスを作成しました。
通知を行うにはさまざまな方法がありますが、以下の例ではメールを用いて通知を送信することを想定しています。

Main.php


<?php

// 通知クラス
class Notification {
  private $platform;

  public function __construct()
  {
    // Emailクラスのインスタンスを作成
    $email = new Email();
    $this->platform = $email;
  }

  // 通知を送信するメソッド
  public function pushNotification()
  {
    $this->platform->notify();
  }
}

// E-mailクラス
class Email {
  public function notify()
  {
    // Email特有の処理 〜
    
    echo "Emailで通知を送信しました。";
  }
}

// メイン処理
$noti = new Notification();
$noti->pushNotification();

実行


$ php Main.php

Emailで通知を送信しました。

上の例において、NotificationクラスはEmailクラスに依存しています。依存というのは、Emailクラスがないと動作できないという意味です。
依存していることで、Emailクラス以外を作りづらいという弊害が発生します。

Emailクラスに加え、Slackクラスを追加したくなった場合、以下のような実装になりえます。

Main.php


<?php

// 通知クラス
class Notification {
  private $platform;

  public function __construct($platformType)
  {
    // 引数によって分岐させる
    if ($platformType === "Email") {
      $this->platform = new Email();
    } elseif ($platformType === "Slack") {
      $this->platform = new Slack();
    }
  }

  public function pushNotification()
  {
    $this->platform->notify();
  }
}

// E-mailクラス
class Email {
  public function notify()
  {
    // Email特有の処理 〜

    echo "Emailで通知を送信しました。";
  }
}

// Slackクラス
class Slack {
  public function notify()
  {
    // Slack特有の処理 〜

    echo "Slackで通知を送信しました。";
  }
}

// メイン処理
$noti = new Notification("Slack");
$noti->pushNotification();

実行


$ php Main.php

Slackで通知を送信しました。

このままでは、Slackクラスの他にもさまざまなクラスを追加する場合、分岐処理がどんどん増えていってしまいます。

依存性の注入

これまでの例では、Notificationクラスの中で他のクラスのインスタンスを作成することで依存が発生していました。
Notificationクラスの中ではなく、メイン処理でインスタンスを作成し、インスタンスをNotificationクラスへ渡すことで依存をなくすことができます。
上記の手法が依存性の注入と呼ばれています。

Main.php


<?php

// 通知クラス
class Notification {
  private $platform;

  public function __construct(Notifiable $notificationPlatform)
  {
    $this->platform = $notificationPlatform;
  }

  public function pushNotification()
  {
    $this->platform->notify();
  }
}

// 通知可能インタフェース
interface Notifiable {
  public function notify();
}

// E-mailクラス
class Email implements Notifiable {
  public function notify()
  {
    // Email特有の処理 〜

    echo "Emailで通知を送信しました。";
  }
}

// Slackクラス
class Slack implements Notifiable {
  public function notify()
  {
    // Slack特有の処理 〜

    echo "Slackで通知を送信しました。";
  }
}

// メイン処理
// メイン処理でインスタンス作成
$platformType = "Slack";
if ($platformType === "Email") {
  $platform = new Email();
} elseif ($platformType === "Slack") {
  $platform = new Slack();
}

// インスタンスを渡す
$noti = new Notification($platform);
$noti->pushNotification();

実行


$ php Main.php

Slackで通知を送信しました。

新たにNotifiableというインタフェースを作成しました。
上記の実装で、Notificationクラスの依存先はNotifiableインタフェースへ変更されました。これにより、NotificationクラスはNotifiableインタフェースを満たしているインスタンスであれば何でも引数で受け取ることができます。

今後新しいクラスがいくら増えても、Notificationクラスは修正する必要がありません。

参考

PHP本格入門[上]
PHP本格入門[下]

php