【ITニュース解説】📝 Beyond WithXyz().Build(): Taking the Fluent Builder Pattern Further in C# (.NET 9)
2025年09月29日に「Dev.to」が公開したITニュース「📝 Beyond WithXyz().Build(): Taking the Fluent Builder Pattern Further in C# (.NET 9)」について初心者にもわかりやすく解説しています。
ITニュース概要
複雑なオブジェクトをきれいに作るC#のFluent Builderパターンは、多くの引数を要するコンストラクタの課題を解決する。バリデーションやResultパターンを組み合わせ、エラーをまとめて扱う安全なオブジェクト構築を可能にする。Blazor等のUI連携にも有効で、実践的な開発に役立つ。
ITニュース解説
C#のプログラミングで、オブジェクトを生成する方法の一つに「ビルダーパターン」というものがある。このパターンは、特に複雑なオブジェクトを作成する際に非常に役立つ。システムエンジニアを目指す初心者の皆さんの中には、オブジェクトのコンストラクタ(オブジェクトを初期化するための特別なメソッド)にたくさんの引数を渡す必要が出てきて、その引数の順序を覚えきれなくなったり、コードが読みにくくなったりする経験をした人もいるかもしれない。まさに、この「コンストラクタの引数過多問題」を解決するためにビルダーパターンが存在する。
従来のビルダーパターンでは、new OrderBuilder().ForCustomer("CUST1").DeliverOn(DateTime.Today.AddDays(2)).Build() のように、メソッドを鎖のように繋げてオブジェクトの各部分を設定し、最後にBuild()メソッドを呼び出して完成させる。これによって、どの設定が何のためのものか明確になり、コードの可読性が大幅に向上する。引数の順番を気にする必要もなくなるため、エラーの発生も減る。
しかし、この基本的なWithXyz().Build()の形は、ビルダーパターンが持つ可能性のほんの一部に過ぎない。実際の開発現場では、もっと賢く、安全に、そして便利な形でビルダーパターンを活用できる。その一つが、オブジェクト構築時にビジネスルールを検証し、その結果を明確に返す「Resultパターン」との組み合わせである。
通常のビルダーでは、もしオブジェクトを構築するためのデータが不適切だった場合、エラーを例外としてスローすることが多い。しかし、例外はプログラムの通常の流れを中断させるため、エラー処理とプログラムの制御フローが混ざってしまい、コードが読みにくくなることがある。そこでResultパターンが登場する。Resultパターンでは、オブジェクトの構築が成功したか失敗したかを明確に表現する特別な型(Result<T>)を返す。成功した場合は作成されたオブジェクトを、失敗した場合はエラーメッセージのリストを返すため、後続の処理でResultオブジェクトの状態をチェックするだけで、エラーを安全に処理できる。
具体的に、Result<T>クラスはIsSuccessという真偽値プロパティと、成功時に値を持つValueプロパティ、失敗時にエラーメッセージのリストを持つErrorsプロパティから構成される。Success(T value)メソッドを呼び出せば成功の状態が、Failure(IEnumerable<string> errors)メソッドを呼び出せば失敗の状態が作成される。
これを実際の注文オブジェクト(Order)を構築するOrderBuilderに適用すると、より堅牢なオブジェクト生成が可能になる。例えば、顧客IDが入力されていない場合、配送日が過去の日付である場合、注文明細が一つもない場合、あるいは複数の明細で異なる通貨が使われている場合など、様々なビジネスルールをBuild()メソッド内で検証できる。OrderBuilderは、これらの検証エラーを一つずつ集約し、もし一つでもエラーがあれば、それら全てのメッセージを含むResult<Order>.Failureを返す。全てが問題なければ、Result<Order>.Successとして完成したOrderオブジェクトを返す。さらに、一度作成に成功したビルダーインスタンスを再利用して、誤って別のオブジェクトを作成してしまうのを防ぐ機能や、ビルダーの状態をリセットする機能も持たせることができる。
このようなResultパターンと組み合わせたビルダーパターンにはいくつかのメリットがある。まず、成功か失敗かを示すIsSuccessプロパティをチェックするだけで済むため、プログラムの制御フローが予測しやすくなる。次に、エラーが発生した場合でも、一つ目のエラーで処理を中断せず、すべての検証エラーを集約して一度に通知できる。これは、ユーザーインターフェース(UI)で複数のエラーメッセージをまとめて表示する際に非常に便利である。さらに、テストコードも書きやすくなるという利点もある。
一方で、Resultパターンには、エラーを扱うためのResultラッパーを導入する必要があるため、記述量が増えるというデメリットもある。また、例外処理に慣れているチームにとっては、最初は少し違和感があるかもしれない。しかし、多くの開発者は、ドメイン固有のビジネスルール検証にはResultパターンを、データベース接続の失敗やネットワーク障害といったシステム的な真に例外的な状況には、これまで通り例外を使用するという使い分けを推奨している。
この進化したビルダーパターンは、UIを持つアプリケーションで特にその真価を発揮する。例えば、C#でWebアプリケーションを開発できるフレームワークであるBlazorのような環境では、ユーザーがフォームにデータを入力し、そのデータがビルダーに渡され、Build()メソッドがResult<Order>を返す。もしエラーがあれば、そのエラーリストがUIに送られ、ユーザーに対してまとめて表示される。これにより、ユーザーは自分の入力のどこに問題があったのかを一度に理解でき、より良いユーザー体験を提供できる。
ビルダーパターンには、他にも様々な高度な応用形が存在する。例えば、一度作成されたオブジェクトを不変(変更不可能)にする「不変ビルダー」や、複数のオブジェクトを段階的に構築する「入れ子ビルダー」、特定の順序でしかメソッドを呼び出せないように強制する「ステップビルダー」、単体テストで使うための、あらかじめデータが設定されたオブジェクトを簡単に作る「テストデータビルダー」、外部サービスを注入できる「DI対応ビルダー」などである。
このパターンは、あらゆる場面で使うべきではない。複雑なビジネスロジックを持つドメインオブジェクトや、複数の部品から構成される「集約」オブジェクトの生成には非常に適しているが、単純なデータ転送オブジェクト(DTO)や、データベースのテーブルに対応するエンティティオブジェクトなど、シンプルで作成が簡単なオブジェクトに対しては、オーバーキル(やりすぎ)になることもある。
まとめると、Fluent Builderパターンは、単にWithXyz().Build()のような形式でオブジェクトを作るだけでなく、ビジネスルールの検証とResultパターンを組み合わせることで、より安全で、保守しやすく、エラー処理の予測が容易なアプリケーションを構築するための強力なツールとなる。コンストラクタの引数が多くて困ったときは、ぜひこのビルダーパターンの活用を検討してほしい。