切り出す理由
コントローラー内に CalculateAnnualPremium が直書きされているため、ユニットテストがしにくい為です。
1. インターフェースの作成
using InsuranceProductManager.Models;
namespace InsuranceProductManager.Services
{
public interface IPremiumCalculator
{
decimal Calculate(Customer customer, InsurancePolicy policy);
}
}
インターフェースとは
「クラスが実装すべき契約(メソッドやプロパティの定義)」を決める仕組みです。
• 共通の仕様を定義して、複数のクラスに統一した振る舞いを持たせられる
• 実際の処理はインターフェースを実装したクラスに任せられる
• 依存性の注入(DI)で柔軟な設計が可能になる
→ つまり「このインターフェースを実装するなら、必ずこのメソッドを持ってね」というルール帳です。
IPremiumCalculator は 「Customer と Policy を渡したら decimal を返す計算器」というルールを定めたものです。
2. 実装クラスの作成
using InsuranceProductManager.Models;
namespace InsuranceProductManager.Services
{
public class PremiumCalculator : IPremiumCalculator
{
public decimal Calculate(Customer customer, InsurancePolicy policy)
{
decimal baseRate = 0.02m;
// 年齢補正
int age = customer.GetAge();
decimal ageFactor = age <= 30 ? 1.0m : age <= 50 ? 1.5m : 2.0m;
// 性別補正(Gender がモデルにあれば考慮できる)
decimal genderFactor = 1.0m;
// 契約期間補正(10年ごとに5%)
int termYears = policy.EndDate.Year - policy.StartDate.Year;
decimal termFactor = 1.0m + (termYears / 10) * 0.05m;
decimal annualPremium = policy.CoverageAmount * baseRate * ageFactor * genderFactor * termFactor;
return Math.Round(annualPremium, 0);
}
}
}
1. public class PremiumCalculator : IPremiumCalculator
これは「PremiumCalculator クラスが IPremiumCalculator インターフェースを実装している」という意味です。
つまり引数ではなく 契約(=インターフェースの定義に従う約束) です。
2. public decimal Calculate(Customer customer, InsurancePolicy policy)
これは インターフェースとクラスで両方に出てくるのが正しい です。
• インターフェース側
• メソッドの「シグネチャ(形だけ)」を宣言
• 本体 { ... } は書けない(C# 8 以降は default 実装可能ですが、基本は空)
• 実装クラス側
• インターフェースで宣言されたメソッドを 必ずオーバーライドして中身を書く必要がある
だから 重複ではなく「契約と実装」 です。