〜エンジニア初学者の開発日記〜 二の巻

こんにちは、つかさです
今回も現在携わっている開発で学んだことを記事にまとめようと思います！
みなさんは開発中に共通化したいと思ったことはありませんか？
2つならまだしも、３つ４つと増えていった場合に、管理しずらいし同じような処理が増えてって嫌ですよね。:frowning2:
なので今回はオブジェクト指向のstrategyパターンについて書いこうと思います。

### 前提
- バックエンド
👉 Laravel
- フロントエンド
👉 Inertia.js(Vue3)
- アーキテクチャ
👉MVCS

## Strategyパターン
商品価格の計算処理を含むシステムを例にとって書いていこうかなと思います。

#### 条件
ユーザータイプによって1000円の商品を買った場合の支払い金額が違うものとする。
1.通常価格計算 (定価で計算)
2.会員価格計算 (会員割引を適用)
3.キャンペーン価格計算 (特定商品への割引を適用)

#### ディレクトリ構造
```html:
app/
├── Http/
│   ├── Controllers/
│   │   └── ProductController.php
├── Services/
│   ├── Pricing/
│   │   ├── PriceCalculator.php
│   │   ├── Strategies/
│   │   │   ├── RegularPricingStrategy.php
│   │   │   ├── MemberPricingStrategy.php
│   │   │   ├── CampaignPricingStrategy.php
│   │   │   └── PriceStrategy.php
```

まず`PriceStrategy.php`で作成し、すべての価格計算方法をここで定義します。
今回は計算処理をする`calculatePrice`関数のみを定義していますが、必要に応じて増やしてここに定義しましょう
```php:PriceStrategy
<?php

namespace App\Services\Pricing\Strategies;

interface PriceStrategy
{
    public function calculatePrice(float $basePrice): float;
}
```
ではそれぞれのユーザータイプによる具体的な処理を`RegularPricingStrategy.php`,`MemberPricingStrategy.php`,`CampaignPricingStrategy.php`に書いていこうと思います。
以下のクラスを見ての通り、`PriceStrategy`インターフェースをimplementsしています。
ルールとしてimplementsしているクラスは、そのインスタンスで定義している関数を実装しなければなりません。よって3つとも`calculatePrice`を実装しています。
なぜこんな事をしているのかというと、ユーザータイプによって違う計算結果は算出したいが、計算を行うということ自体は共通の処理として担保したい。かつそれぞれのユーザータイプに計算処理は疎結合になっています。
例えば、キャンペーン対象のユーザーには30%offにしたい場合`CampaignPricingStrategy.php`の部分を変更するだけで済みます。なんら他の箇所に影響を及ぼすことはありません。

```php:RegularPricingStrategy
<?php

namespace App\Services\Pricing\Strategies;

class RegularPricingStrategy implements PriceStrategy
{
    public function calculatePrice(float $basePrice): float
    {
        return $basePrice; // 定価そのまま
    }
}
```
```php:MemberPricingStrategy
<?php

namespace App\Services\Pricing\Strategies;

class MemberPricingStrategy implements PriceStrategy
{
    public function calculatePrice(float $basePrice): float
    {
        return $basePrice * 0.9; // 10%割引
    }
}
```
```php:CampaignPricingStrategy
<?php

namespace App\Services\Pricing\Strategies;

class CampaignPricingStrategy implements PriceStrategy
{
    public function calculatePrice(float $basePrice): float
    {
        return $basePrice * 0.8; // 20%割引
    }
}
```
次にContextクラスを書きます。利用するStrategyを動的に切り替えられるようにします。
まず`PriceStrategy`をコンストラクタでインスタンス化します。
`setStrategy`はStrategyを切り替えるメソッドになります。
`calculate`は`PriceStrategy`で定義したメソッドを飛び出すメソッドです。
`

```php:PriceCalculator
<?php

namespace App\Services\Pricing;

use App\Services\Pricing\Strategies\PriceStrategy;

class PriceCalculator
{
    public function __construct(private PriceStrategy $strategy)
    {
    }

    public function setStrategy(PriceStrategy $strategy): void
    {
        $this->strategy = $strategy;
    }

    public function calculate(float $basePrice): float
    {
        return $this->strategy->calculatePrice($basePrice);
    }
}
```
最後にコントローラークラスです。
ユーザーのタイプによってmatch文でどのstrategyクラスを使用するのかを判断しています。
strategyクラスを引数で渡した`PriceCalculator`をインスタンス化し、calculateを呼び出して1000を渡してます。結果として支払い金額が返ってきます。
見る通り、コントローラークラスはContextクラスを呼び出しているだけです。
```php:ProductController
<?php

namespace App\Http\Controllers;

use App\Services\Pricing\PriceCalculator;
use App\Services\Pricing\Strategies\RegularPricingStrategy;
use App\Services\Pricing\Strategies\MemberPricingStrategy;
use App\Services\Pricing\Strategies\CampaignPricingStrategy;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function showPrice(Request $request)
    {
        $basePrice = 1000; // 商品の基本価格（例）

        // ユーザーの条件に基づいてStrategyを切り替え
        $strategy = match ($request->get('user_type')) {
            'member' => new MemberPricingStrategy(),
            'campaign' => new CampaignPricingStrategy(),
            default => new RegularPricingStrategy(),
        };

        // PriceCalculatorを使用して価格を計算
        $calculator = new PriceCalculator($strategy);
        $finalPrice = $calculator->calculate($basePrice);

        return response()->json([
            'base_price' => $basePrice,
            'final_price' => $finalPrice,
        ]);
    }
}
```
### まとめ

読んでいただきありがとうございました！
これからも自分のペースで記事を更新したり、新たな記事を書いていこうと思うので是非見ていただけたらなと思います！:grin: