ビルダー
説明
ビルダーヘルパーへの呼び出しによってオブジェクトを構築します。
例
#![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
}
// ここで Builder を消費せずに済ませられるなら、それは利点である。
// つまり、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);
}
}
動機
そうでなければ多くのコンストラクターが必要になる場合や、構築に副作用がある場合に有用です。
利点
構築用のメソッドを他のメソッドから分離します。
コンストラクターの増殖を防ぎます。
1 行での初期化にも、より複雑な構築にも使用できます。
対象の構造体に新しいフィールドを追加する場合、クライアントコードの後方互換性を維持したままビルダーを更新できます。
欠点
構造体オブジェクトを直接作成する場合や、単純なコンストラクター関数よりも複雑です。
議論
Rust にはオーバーロードや関数パラメーターのデフォルト値がないため、このパターンは他の多くの言語よりも Rust で(そしてより単純なオブジェクトに対して)頻繁に見られます。ある名前を持つメソッドは 1 つしか持てないため、複数のコンストラクターを持つことは、Rust では C++、Java、その他の言語ほど扱いやすくありません。
このパターンは、ビルダーオブジェクトが単なるビルダーではなく、それ自体で有用な場合によく使用されます。たとえば、
std::process::Command
は
Child(プロセス)
のビルダーです。このような場合、T と TBuilder という命名パターンは使用されません。
この例では、ビルダーを値として受け取り、値として返します。ビルダーを可変参照として受け取り、返す方が、多くの場合よりエルゴノミック(かつより効率的)です。借用チェッカーにより、これは自然に機能します。このアプローチには、次のようなコードを書けるという利点があります。
let mut fb = FooBuilder::new();
fb.a();
fb.b();
let f = fb.build();
また、FooBuilder::new().a().b().build() スタイルも使用できます。
関連項目
- スタイルガイドでの説明
- derive_builder、ボイラープレートを避けながらこのパターンを自動的に実装するための crate。
- 構築がより単純な場合のための Constructor パターン。
- Builder パターン(wikipedia)
- 複雑な値の構築