Stateパターン

StateパターンをECサイトの構築を例にして説明します。

会員の状態を表示しよう

会員のランクにしたがって、名前の隣にラベルとポイント付与率を出して、ユーザーの購入意欲を煽ろうと思いました。
そこで、ステータスに関する情報を返戻するクラスを作ってみました。

class User
{
  const NORMAL = 1;
  const BLOND = 2;
  const SILVER = 3;
  const GOLD = 4;

  private int $membershipType;

  function setMembershipType(int $membershipType)
  {
    $this->membershipType = $membershipType;
  }

  function getTypeName(): string
  {
    if ($this->membershipType == self::NORMAL) {
      return 'ノーマル';
    } else if ($this->membershipType == self::BLOND) {
      return 'ブロンド';
    } else if ($this->membershipType == self::SILVER) {
      return 'シルバー';
    } else if ($this->membershipType == self::GOLD) {
      return 'ゴールド';
    }
  }

  function getPointRate(): float
  {
    if ($this->membershipType == self::NOMAL) {
      return 0.05;
    } else if ($this->membershipType == self::BLOND) {
      return 0.08;
    } else if ($this->membershipType == self::SILVER) {
      return 0.1;
    } else if ($this->membershipType == self::GOLD) {
      return 0.15;
    }
  }
}

悪くはなさそうですね。
しかし、if文があちこちに散らばり保守性がいまいちに見えます。
可読性と保守性を高めるにはStateパターンを検討すると良いでしょう。

Stateパターンとは

システムの中で取りうる状態を事前に定義しておいて切り替えるというパターンです。

interface MembershipStatus
{
  public function getTypeName(): string;
  public function getPointRate(): float;
}

class MembershipStatusNormal implements MembershipStatus
{
  public function getTypeName(): string
  {
    return 'NORMAL';
  }

  public function getPointRate(): float
  {
    return 0.05;
  }
}

class MembershipStatusBlond implements MembershipStatus
{
  public function getTypeName(): string
  {
    return 'BLOND';
  }
  public function getPointRate(): float
  {
    return 0.08;
  }
}

class MembershipStatusSilver implements MembershipStatus
{
  public function getTypeName(): string
  {
    return 'SILVER';
  }
  public function getPointRate(): float
  {
    return 0.1;
  }
}

class MembershipStatusGold implements MembershipStatus
{
  public function getTypeName(): string
  {
    return 'GOLD';
  }
  public function getPointRate(): float
  {
    return 0.15;
  }
}

class User
{
  private MembershipStatus $status;

  public function setMembershipType(MembershipStatus $status)
  {
    $this->status = $status;
  }

  public function getTypeName(): string
  {
    return $this->status->getTypeName();
  }

  public function getPointRate(): float
  {
    return $this->status->getPointRate();
  }
}

Stateパターンを使うことで、コード自体は増えましたが、Userのコードはif文が無くなり、だいぶスッキリしました。
しかし、メリットはif文が無くなることだけではありません。

例えば会員種別に「ダイアモンド」が増えたとしたらどうでしょう。
if文の場合だとgetTypeNameやgetPointRateの中のif-else文すべてに改修が必要となります。

そこで、Stateパターンを使えば、MembershipStatusを実装したStateを増やすだけでOKです。それぞれのfunctionへの改修は必要なくなります。
そのおかげで改修漏れなどの不具合が発生する危険を回避することができます。

このように何かの「状態」によってシステムに変化がある場合はStateパターンを検討すると良いでしょう。