Builder
説明
builder ヘルパの呼び出しによりオブジェクトを構築します。
例
#![allow(unused)] fn main() { #[derive(Debug, PartialEq)] pub struct Foo { // 複雑なフィールドがたくさん。 bar: String, } impl Foo { // このメソッドは、ユーザがビルダーを発見しやすくする pub fn builder() -> FooBuilder { FooBuilder::default() } } #[derive(Default)] pub struct FooBuilder { // おそらく任意選択のフィールドがたくさん。 bar: String, } impl FooBuilder { pub fn new(/* ... */) -> FooBuilder { // Foo の最低限必要なフィールドを設定する。 FooBuilder { bar: String::from("X"), } } pub fn name(mut self, bar: String) -> FooBuilder { // ビルダー自身に名前を設定し、ビルダーを値で返す。 self.bar = bar; self } // もしここでビルダーを消費せずに済むのであれば、これは利点となる。 // つまり、FooBuilderをテンプレートとして使って、 // 複数のFooを構築することができる。 pub fn build(self) -> Foo { // FooBuilder から Foo を作成する。このとき FooBuilder のすべての設定を // Foo に適用する。 Foo { bar: self.bar } } } #[test] fn builder_test() { let foo = Foo { bar: String::from("Y"), }; let foo_from_builder: Foo = FooBuilder::new().name(String::from("Y")).build(); assert_eq!(foo, foo_from_builder); } }
動機形成
多くのコンストラクタが必要な状況や、インスタンス構築時に副作用がある状況に便利です。
長所
構築のためのメソッドを他のメソッドから分離します。
コンストラクタの増殖を防ぎます。
ワンライナーでの初期化にも、より複雑な構築にも使用できます。
短所
構造体オブジェクトを直接作成したり、単純なコンストラクタ関数を使用するよりも複雑です。
議論
Rustにはオーバーロードがないため、他の多くの言語と比較して、このパターンが頻繁に登場します(またより単純なオブジェクトに適用されます)。1つの名前を持つメソッドは1つのみであることから、Rust にて複数のコンストラクタを定義することは、C++ や Java などと比べてあまり良いことではありません。
このパターンは、builder オブジェクトが単なる builder としてではなく、それ自体として有用である場合によく使われます。例えばstd::process::Command
は Child
(プロセス) のビルダーです。これらの場合、 T
と TBuilder
の命名パターンは使用されていません。
例ではbuilderを値として引数に受け、return しています。可変参照として引数に受け、return する形式が、多くの場合、より人間工学的(かつ効率的)です。借用チェッカーはこれを自然に動作させます。このアプローチには下記のようなコードを書けるようになるメリットがあります
let mut fb = FooBuilder::new();
fb.a();
fb.b();
let f = fb.build();
FooBuilder::new().a().b().build()
のスタイルと同様です。
See also
- Style Guideでの記述
- derive_builderは、このパターンを自動的に実装するためのクレートです。ボイラーテンプレートを省けます。
- コンストラクタ パターン 構築がより単純な場合はこちら。
- Builder pattern (wikipedia)
- 複雑な値の構築