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::CommandChild (プロセス) のビルダーです。これらの場合、 TTBuilder の命名パターンは使用されていません。

例ではbuilderを値として引数に受け、return しています。可変参照として引数に受け、return する形式が、多くの場合、より人間工学的(かつ効率的)です。借用チェッカーはこれを自然に動作させます。このアプローチには下記のようなコードを書けるようになるメリットがあります

let mut fb = FooBuilder::new();
fb.a();
fb.b();
let f = fb.build();

FooBuilder::new().a().b().build() のスタイルと同様です。

See also

Last change: 2024-07-09, commit: 317c88e