Template Methodパターン

会員ランクごとに画面イメージを切り替えよう

会員のランクによって画面(Web)のイメージを変えたいと思いました。
さっそく書いてみましょう。

class Template
{
  const Bronze = 1;
  const Silver = 2;
  const Gold = 3;
  private int $rank;
  public function setRank(int $rank)
  {
    $this->rank = $rank;
  }
  public function view()
  {
    switch($this->rank) {
      case Template::Bronze:
        echo 'Bronzeランクヘッダー' . "\n";
        echo 'Bronzeランクボディ' . "\n";
        echo '共通フッター' . "\n";
        break;
      case Template::Silver:
        echo 'Silverランクヘッダー' . "\n";
        echo 'Silverランクボディ' . "\n";
        echo '共通フッター' . "\n";
        break;
      case Template::Gold:
        echo 'Goldランクヘッダー' . "\n";
        echo 'Goldランクボディ' . "\n";
        echo '共通フッター' . "\n";
        break;
    }
  }
}


$template = new Template();
$template->setRank(Template::Gold);
$template->view();

例ではシンプルですが、これがHTMLだとすると非常に強大なswitch分になってしまいますね。

では、Template Methodパターンでの実装を見てみましょう。

Template Methodパターンとは

抽象クラスで処理の流れを定義し、継承した具象クラスで処理の中身を実装するパターンです。

abstract class AbstractTemplate
{
  public abstract function header(): void;
  public abstract function body(): void;
  public abstract function footer(): void;
  public function view(): void
  {
    $this->header();
    $this->body();
    $this->footer();
  }
}

class BronzeRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Bronzeランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Bronzeランクボディ' . "\n";
  }
  public function footer(): void
  {
        echo '共通フッター' . "\n";
  }
}

class SilverRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Silverランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Silverランクボディ' . "\n";
  }
  public function footer(): void
  {
        echo '共通フッター' . "\n";
  }
}

class GoldRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Goldランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Goldランクボディ' . "\n";
  }
  public function footer(): void
  {
        echo '共通フッター' . "\n";
  }
}

$template = new GoldRankTemplate();
$template->view();

まず、画面を表示する「Template」クラスに表示と関係ない処理を持たなくて良くなります。
つまりクラスの責務として描画に集中できているわけです。

また、抽象クラス側でメソッドの実行順序を決めているので、実行順序の誤りがなくなります。
たとえば、ヘッダーを描画する前にボディーを描画してしまうようなミスを無くせます。

抽象クラスの継承

Template Methodパターンは、インタフェースではなく抽象クラスを継承する形となっています。
これを利用して、例の中の「共通フッター」を抽象クラスに持たせることも可能です。

<?php

abstract class AbstractTemplate
{
  public abstract function header(): void;
  public abstract function body(): void;
  public function footer(): void
  {
        echo '共通フッター' . "\n";
  }
  public function view(): void
  {
    $this->header();
    $this->body();
    $this->footer();
  }
}

class BronzeRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Bronzeランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Bronzeランクボディ' . "\n";
  }
}

class SilverRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Silverランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Silverランクボディ' . "\n";
  }
}

class GoldRankTemplate extends AbstractTemplate
{
  public function header(): void
  {
        echo 'Goldランクヘッダー' . "\n";
  }
  public function body(): void
  {
        echo 'Goldランクボディ' . "\n";
  }
  public function footer(): void
  {
        echo 'Goldランクフッター' . "\n";
  }
}

$template = new BronzeRankTemplate();
$template->view();

footerメソッドの実装をAbstractTemplateに持たせました。
BronzeランクとSilverランクはfooterメソッドを実装しないことで、共通フッターを表示します。
こうすることで「同じような実装」が重複しなくなります。
一方、別のfooterを表示したい場合は、GoldRankTemplateのように、オーバーライドすることで、実現できます。

このように、実装を持つことができるのが抽象クラスがインタフェースと違う点です。

今回は、抽象クラスの説明も行ってしまいました笑

処理の流れを担保しつつ、柔軟な実装を持たせたい場合はTemplate Methodパターンを検討してみると良いでしょう。