web-dev-qa-db-ja.com

ビルダーパターンと多数の必須パラメーター

これまで、ビルダーパターンの following 実装を使用しました(説明した実装とは対照的に here ):

_public class Widget {
    public static class Builder {
        public Builder(String name, double price) { ... }
        public Widget build() { ... }
        public Builder manufacturer(String value) { ... }
        public Builder serialNumber(String value) { ... }
        public Builder model(String value) { ... }
    }

    private Widget(Builder builder) { ... }
}
_

これは、さまざまな必須/必須およびオプションのパラメーターを使用して複雑なオブジェクトを作成する必要があるほとんどの状況でうまく機能します。ただし、すべてのパラメーターが必須(または少なくとも大部分が必須)である場合、パターンがどのように役立つかを理解するのに最近苦労しています。

これを回避する1つの方法は、ビルダーコンストラクターに渡されるパラメーターの数を減らすために、独自のクラスに渡されるパラメーターを論理的にグループ化することです。

たとえば、次の代わりに:

_Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                           .addOptional(opt9)
                           .build();
_

次のようにグループ化されます。

_Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);

Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();
_

個別のオブジェクトを使用すると、物事がかなり簡素化されますが、コードに慣れていない場合、物事を追跡するのが少し難しくなります。私が検討したことの1つは、すべてのパラメーターを独自のaddParam(param)メソッドに移動し、build()メソッドの必須パラメーターで検証を実行することでした。

ベストプラクティスとは何ですか。これについては、おそらく私が検討していないより良いアプローチがありますか。

54
speedRS

ただし、すべてのパラメーターが必須(または少なくとも大部分が必須)である場合、パターンがどのように役立つかを理解するのに最近苦労しています。

流fluentなビルダーパターンは依然として有益です。

  1. より読みやすい-呼び出しが名前のない引数の単なる長いリストではないように、名前付きパラメーターを効果的に許可します

  2. 順序付けられていない-これにより、単一のBuilderセッター呼び出しの一部として、または単純にこの特定のインスタンス化を最も意味するBuilderセッターメソッドを呼び出す自然な順序を使用することにより、引数を論理グループにまとめることができます。


_Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                               .addOptional(opt9)
                               .build();
_

次のようにグループ化されます。

_Object1 group1  = new Object1(req1, req2, req3, req4);
Object2 group2  = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();
_

個別のオブジェクトを使用すると、物事がかなり簡素化されますが、コードに慣れていない場合、物事を追跡するのが少し難しくなります。私が検討したことの1つは、すべてのパラメーターを独自のaddParam(param)メソッドに移動し、build()メソッドの必須パラメーターで検証を実行することでした。

適切または自然な場合、私はハイブリッドを好みます。すべてコンストラクターである必要はありませんまたは各パラメーターには独自のaddParamメソッドがあります。 Builderを使用すると、一方、他方、中間、またはコンボを柔軟に実行できます。

_Widget.Builder builder = new Widget.Builder(Widget.BUTTON);

builder.withWidgetBackingService(url, resource, id);
builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
builder.withMouseover("Not required");

Widget example = builder.build();
_
28
Bert F

多くの必須パラメーターがある場合は、 Step Builder を使用できます。つまり、必須パラメーターごとにインターフェイスを定義すると、ビルダーメソッドは次の必須ビルダーインターフェイスまたはオプションメソッドの場合はビルダー自体を返します。ビルダーは、すべてのインターフェースを実装する単一のクラスのままです。

KotlinやScalaなどの言語は、デフォルト値の名前付きパラメーターを提供するため、ここではより便利です。

35
deamon

昇格することはめったにありませんが、ビルダーパターンの利点の1つは、たとえばすべての必須パラメーターが正しい場合、または他の必要なリソースが利用可能な場合にのみ、オブジェクトを条件付きで構築するためにも使用できることです。その点で、それらは 静的ファクトリーメソッド と同様の利点を提供します。

3
Toenex

あなたのすべてのパラメーターが必須である場合、パターンがどのように有益であるかを理解するのに最近苦労しています

このパターンは、不変クラスの作成を容易にし、読み取り可能なコードを促進します。以下のPersonクラスを検討してください(従来のコンストラクターとビルダーを使用)。

public static class Person {

    private static final class Builder {
        private int height, weight, age, income, rank;
        public Builder setHeight(final int height) { this.height = height; return this; }
        public Builder setWeight(final int weight) { this.weight = weight; return this; }
        public Builder setAge(final int age) { this.age = age; return this; }
        public Builder setIncome(final int income) {    this.income = income; return this; }
        public Builder setRank(final int rank) { this.rank = rank; return this; }
        public Person build() { return new Person(this); }
    }

    private final int height;
    private final int weight;
    private final int age;
    private final int income;
    private final int rank;

    public Person(final int height, final int weight, final int age, final int income, final int rank) {
        this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
    }

    private Person(final Builder builder) {
        height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
        // Perform validation
    }

    public int getHeight() { return height; }
    public int getWeight() { return weight; }
    public int getAge() { return age; }
    public int getIncome() { return income; }
    public int getRank() {  return rank; }

}

どの構築方法が理解しやすいですか?

final Person p1 = new Person(163, 184, 48, 15000, 23);
final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
    setIncome(15000).setRank(23).build();

これを回避する1つの方法は、渡されるパラメーターを独自のクラスに論理的にグループ化することです。

確かに、これは 凝集 の原則であり、オブジェクト構築のセマンティクスに関係なく採用されるべきです。

3
hoipolloi

ビルダー/ファクトリーでは、Widgetがインターフェースになり、new Widget.Builderを挿入または非表示にする方法があると仮定して、インターフェースを実装タイプから分離(またはアダプターのプラグインなど)することができます。

デカップリングを気にせず、実装が1回限りの場合、あなたは正しい:ビルダーパターンは通常のコンストラクターよりもはるかに便利ではありません(まだ引数をビルダーごとの属性でラベル付けしています) -メソッドスタイル。)

引数をほとんど変えずにオブジェクトを繰り返し作成する場合、それはまだ役に立つかもしれません。いくつかの属性をプラグインした後に取得した中間ビルダーを渡したり、キャッシュしたりできます。

Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz");

// ...

Widget w1 = base.serialNumber("bar").build();
Widget w2 = base.serialNumber("baz").build();
Widget w3 = base.serialNumber("quux").build();

これは、ビルダーが不変であることを前提としています。ビルダーのセッターは属性を設定してthisを返すのではなく、代わりに自身の新しいコピーを返します。上で指摘したように、パラメータオブジェクトは、繰り返される引数ボイラープレートを回避する別の方法です。そこでは、ビルダーパターンさえ必要ありません。パラメーターオブジェクトを実装コンストラクターに渡すだけです。

0
phs