導入
「有効な会員」「送料無料の注文」——業務の条件があちこちに if で散らばると、同じ判定を何度も書くことになります。Specification パターンは、条件を「判定できるオブジェクト」に切り出し、名前を付けて再利用・組み合わせられるようにします。
図解
flowchart LR
S1["ISpecification<Order>"] --> A["IsSatisfiedBy(order) → true/false"]
S1 -.-> B["高額注文の仕様"]
S1 -.-> C["優先会員の仕様"]
B & C --> D["組み合わせて再利用"]
style S1 fill:#e1f5fe
サンプル
var orders = new List<Order>
{
new Order(1500), new Order(500), new Order(3000)
};
var expensive = new ExpensiveSpec(1000); // 「1000円超」という条件をオブジェクト化
var picked = orders.Where(expensive.IsSatisfiedBy).ToList();
Console.WriteLine(picked.Count); // 2
record Order(int Amount);
interface ISpecification<T> { bool IsSatisfiedBy(T target); }
class ExpensiveSpec : ISpecification<Order>
{
private readonly int _threshold;
public ExpensiveSpec(int threshold) => _threshold = threshold;
public bool IsSatisfiedBy(Order o) => o.Amount > _threshold;
}
- 条件を
IsSatisfiedByを持つオブジェクトに切り出す - 名前が付くので意図が明確(
ExpensiveSpec)で、Whereなどにそのまま渡せる - 仕様どうしを AND/OR で組み合わせて再利用できる
やってみよう
Amount がちょうど割り切れる等の別の仕様クラスを作り、orders.Where(spec.IsSatisfiedBy) に差し替えて結果の違いを見ましょう。
演習
record Person(int Age);
interface ISpecification<T> { bool IsSatisfiedBy(T target); }
// TODO: Age が 18 以上なら true を返す AdultSpec を実装してください
___
var people = new List<Person> { new Person(15), new Person(20), new Person(40) };
var adults = people.Where(new AdultSpec().IsSatisfiedBy).Count();
Console.WriteLine(adults); // 2
- 期待される出力:
2
ヒント1を見る
class AdultSpec : ISpecification<Person> { public bool IsSatisfiedBy(Person p) => p.Age >= 18; }
ヒント2を見る
IsSatisfiedBy は判定結果の bool を返します
まとめ
- Specificationは業務条件を「判定できるオブジェクト」に切り出す
- 名前が付き、再利用・組み合わせ(AND/OR)ができる
- 散らばった
ifを1か所に集約できる
次回: 例外に頼らずに失敗を表す Result/Option です。