こんにちは、つかさです
今回も現在携わっている開発で学んだことを記事にまとめようと思います!
みなさんは開発中に共通化したいと思ったことはありませんか?
2つならまだしも、3つ4つと増えていった場合に、管理しずらいし同じような処理が増えてって嫌ですよね。
なので今回はオブジェクト指向のstrategyパターンについて書いこうと思います。
前提
- バックエンド
👉 Laravel - フロントエンド
👉 Inertia.js(Vue3) - アーキテクチャ
👉MVCS
Strategyパターン
商品価格の計算処理を含むシステムを例にとって書いていこうかなと思います。
条件
ユーザータイプによって1000円の商品を買った場合の支払い金額が違うものとする。
1.通常価格計算 (定価で計算)
2.会員価格計算 (会員割引を適用)
3.キャンペーン価格計算 (特定商品への割引を適用)
ディレクトリ構造
app/
├── Http/
│ ├── Controllers/
│ │ └── ProductController.php
├── Services/
│ ├── Pricing/
│ │ ├── PriceCalculator.php
│ │ ├── Strategies/
│ │ │ ├── RegularPricingStrategy.php
│ │ │ ├── MemberPricingStrategy.php
│ │ │ ├── CampaignPricingStrategy.php
│ │ │ └── PriceStrategy.php
まずPriceStrategy.php
で作成し、すべての価格計算方法がこれを実装します。
今回は計算処理をするcalculatePrice
関数のみを定義していますが、必要に応じて増やしてここに定義しましょう
<?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
namespace App\Services\Pricing\Strategies;
class RegularPricingStrategy implements PriceStrategy
{
public function calculatePrice(float $basePrice): float
{
return $basePrice; // 定価そのまま
}
}
<?php
namespace App\Services\Pricing\Strategies;
class MemberPricingStrategy implements PriceStrategy
{
public function calculatePrice(float $basePrice): float
{
return $basePrice * 0.9; // 10%割引
}
}
<?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
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
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,
]);
}
}
まとめ
読んでいただきありがとうございました!
これからも自分のペースで記事を更新したり、新たな記事を書いていこうと思うので是非見ていただけたらなと思います!