Bridgeパターン

顧客への連絡方法を柔軟に切り替えよう

顧客への通常の連絡や、注文受付メールなどの固定文言の連絡送る仕組みを作ります。
メールやSMSなど方法は柔軟に選べるような作りにしたいと考えています。

今回はBridgeパターンで作ってみましょう。

Bridgeパターンとは

機能と実装を橋渡しすることで、機能と実装を分離させるパターンです。

interface MessengerImpl
{
  public function sendMessage(string $customerName, string $message): void;
  public function sendOrderAcceptanceMessage(string $customerName): void;
}

class MailMessengerImpl implements MessengerImpl
{
  public function sendMessage(string $customerName, string $message): void
  {
    echo $customerName . 'に「'.$message.'」というメールを送ります';
  }
  public function sendOrderAcceptanceMessage(string $customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

class ChatMessengerImpl implements MessengerImpl
{
  public function sendMessage(string $customerName, string $message): void
  {
    echo $customerName . 'に「'.$message.'」というチャットを送ります';
  }
  public function sendOrderAcceptanceMessage(string $customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

class SmsMessengerImpl implements MessengerImpl
{
  public function sendMessage(string $customerName, string $message): void
  {
    echo $customerName . 'に「'.$message.'」というSMSを送ります';
  }
  public function sendOrderAcceptanceMessage($customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

/**
 * メッセージ送信.
 */
class Messenger
{
  protected MessengerImpl $impl;

  public function Messenger(MessengerImpl $impl)
  {
    $this->impl = $impl;
  }

  public function sendMessage(string $customerName, string $message):void
  {
    $this->impl->sendMessage($customerName, $message);
  }
}

/**
 * 注文受付メッセージ送付機能を持つクラス.
 */
class OrderAcceptanceMessenger extends Messenger
{
  public function sendOrderAcceptanceMessage(string $customerName): void
  {
    $this->impl->sendOrderAcceptanceMessage($customerName);
  }
}

$messenger = new Messenger(new MailMessengerImpl());
$messenger->sendMessage('お客様', 'メッセージ内容');

echo "\n";

$messenger = new Messenger(new SmsMessengerImpl());
$messenger->sendMessage('お客様', 'メッセージ内容');

echo "\n";

$messenger = new OrderAcceptanceMessenger(new MailMessengerImpl());
$messenger->sendOrderAcceptanceMessage('お客様');

echo "\n";

$messenger = new OrderAcceptanceMessenger(new ChatMessengerImpl());
$messenger->sendOrderAcceptanceMessage('お客様');

このような作りにすることで、「何を送るか」と「どのように送るか」の組み合わせを自由に行うことができます。

Bridgeパターンを使わないと、次のようになります。

class MailMessenger
{
  public function sendMessage($customerName, $message): void
  {
    echo $customerName . 'に「' . $message . '」というメールを送ります';
  }
}
class OrderAcceptanceMailMessenger extends MailMessenger
{
  public function sendOrderAcceptanceMessage($customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

class ChatMessenger
{
  public function sendMessage($customerName, $message): void
  {
    echo $customerName . 'に「' . $message . '」というチャットを送ります';
  }
}
class OrderAcceptanceChatMessenger extends ChatMessenger
{
  public function sendOrderAcceptanceMessage($customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

class SmsMessenger
{
  public function sendMessage($customerName, $message): void
  {
    echo $customerName . 'に「' . $message . '」というSMSを送ります';
  }
}
class OrderAcceptanceSmsMessenger extends SmsMessenger
{
  public function sendOrderAcceptanceMessage($customerName): void
  {
    $this->sendMessage($customerName, '注文を受け付けました');
  }
}

$messenger = new MailMessenger();
$messenger->sendMessage('お客様', 'メッセージ内容');

echo "\n";

$messenger = new SmsMessenger();
$messenger->sendMessage('お客様', 'メッセージ内容');

echo "\n";

$messenger = new OrderAcceptanceMailMessenger();
$messenger->sendOrderAcceptanceMessage('お客様');

echo "\n";

$messenger = new OrderAcceptanceChatMessenger();
$messenger->sendOrderAcceptanceMessage('お客様');

さて、出荷完了メールを増やしたくなった場合、クラスの数はどうなるでしょう?

Bridgeパターンでは、機能側のクラスを増やし、各実装クラスにメソッドを追加します。
一方、Bridgeパターンを使わない方法の場合、クラスの数は機能の数と送信方法の数の積になってしまいます。

機能と実装を自由に組み合わせるような要件の場合にはBridgeパターンを検討してみると良いでしょう。