Strategyパターン

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

配送地域によって送料を変えたい

扱っている商品は関東から配送されるとします。

配送する地方と重量によって送料の計算方法を変えたくなりました。
そこで次のようなコードを書きました。

class ShippingCalculator
{
  const KANTO = 1;
  const HOKKAIDO = 2;
  const KANSAI = 3;
  const OKINAWA = 4;

  public function calculateShipping(int $area, int $weight): int
  {
    $shipping = 0;
    switch ($area) {
      case self::KANTO:
        $shipping = 200;
        break;
      case self::HOKKAIDO:
        $shipping = 800;
        if ($weight > 3000) {
          $shipping += 500;
        }
        break;
      case self::KANSAI:
        $shipping = 400;
        break;
      case self::OKINAWA:
        $shipping = 1200;
        if ($weight > 3000) {
          $shipping += 500;
        } elseif ($weight > 5000) {
          $shipping += 800;
        }
        break;
      default:
        throw new \Exception();
    }

    return $shipping;
  }
}

Strategyパターンとは

アルゴリズムを柔軟に変更するパターンです。

使ってみよう

interface ShippingType
{
  public function calculateShipping(int $weight): int;
}

class ShippingTypeKanto implements ShippingType
{
  public function calculateShipping(int $weight): int
  {
    return 200;
  }
}

class ShippingTypeHokkaido implements ShippingType
{
  public function calculateShipping(int $weight): int
  {
    $shipping = 800;
    if ($weight > 3000) {
      $shipping += 500;
    }
    return $shipping;
  }
}

class ShippingTypeKansai implements ShippingType
{
  public function calculateShipping(int $weight): int
  {
    return 400;
  }
}

class ShippingTypeOkinawa implements ShippingType
{
  public function calculateShipping(int $weight): int
  {
    $shipping = 1200;
    if ($weight > 3000) {
      $shipping += 500;
    } elseif ($weight > 5000) {
      $shipping += 800;
    }
    return $shipping;
  }
}


class ShippingCalculator
{
  private ShippingType $shippingType;
  public function setShippingType(ShippingType $shippingType)
  {
    $this->shippingType = $shippingType;
  }
  public function calculateShipping(int $weight): int
  {
    return $this->shippingType->calculateShipping($weight);
  }
}

Strategyパターンを使うことで、コード自体は増えましたが、calculateShippingメソッドのコードはif文が無くなり、だいぶスッキリしました。

ここで、関東・関西などのように地域ではなく各県にする、という変更が入っても、クラスを増やすことでたやすく実現できます。
また、ある地域・県の送料計算が複雑になっても、その地域・県を表すクラスの中でロジックが完結し、calculateShipping内には影響がありません(当然他のクラスにも影響は与えません)。

このように何かの「ロジック」「アルゴリズム」を柔軟に変更するような場合はStateパターンを検討すると良いでしょう。