いつビルダーパターンを使用しますか?[閉まっている]


531

いくつかのどのようなものがあり、共通の現実の世界の例 Builderパターンを使用したのは?何で買うの?なぜファクトリーパターンを使用しないのですか?


stackoverflow.com/questions/35238292/...は、使用ビルダーパターンといういくつかのAPIを述べた
アリレザFattahi

AaronTethaからの回答は非常に有益です。これらの回答に関連する記事全体を以下に示します。
ディアブロ2017年

回答:


262

ビルダーとファクトリーIMHOの主な違いは、オブジェクトを構築するために多くのことを行う必要がある場合にビルダーが役立つことです。たとえば、DOMを想像してみてください。最終的なオブジェクトを取得するには、多くのノードと属性を作成する必要があります。ファクトリは、ファクトリが1つのメソッド呼び出しでオブジェクト全体を簡単に作成できる場合に使用されます。

ビルダーを使用する1つの例は、XMLドキュメントの構築です。HTMLフラグメントを構築するときにこのモデルを使用しました。たとえば、特定のタイプのテーブルを構築するためのビルダーがあり、次のメソッドがある場合があります(パラメーターは示していません)。

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

このビルダーは、HTMLを出力してくれます。これは、大規模な手続き型メソッドを使用するよりもはるかに読みやすくなっています。

ウィキペディアでビルダーパターンを確認してください。


1021

以下は、Javaでのパターンとサンプルコードの使用を主張するいくつかの理由ですが、これは、Gang of Fourのデザインパターンでカバーされるビルダーパターンの実装です。。Javaで使用する理由は、他のプログラミング言語にも当てはまります。

Joshua Blochが「Effective Java、2nd Edition」で述べているように

ビルダーパターンは、コンストラクターや静的ファクトリーが少数のパラメーターを超えるクラスを設計する場合に適しています。

ある時点で、コンストラクターのリストを含むクラスに遭遇しました。クラスを追加するたびに、新しいオプションパラメーターが追加されます。

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

これは、テレスコーピングコンストラクターパターンと呼ばれます。このパターンの問題は、コンストラクターが4または5パラメーターの長さになると、必要なパラメーターの順序を覚えることが難しくなることです。特定の状況でどの特定のコンストラクターが。

Telescopingコンストラクターパターンに代わる 1つの方法は、必須パラメーターを使用してコンストラクターを呼び出し、その後にオプションのセッターを呼び出すJavaBeanパターンです。

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

ここでの問題は、オブジェクトが複数の呼び出しで作成されているため、その構築の途中で一貫性のない状態になる可能性があることです。これには、スレッドの安全性を確保するために多くの追加の労力が必要です。

より良い代替案は、ビルダーパターンを使用することです。

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

    public Pizza build() {
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

Pizzaは不変であり、パラメータ値はすべて1つの場所にあることに注意してください。BuilderのセッターメソッドはBuilderオブジェクトを返すため、チェーン化できます

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

これにより、作成が簡単で、非常に読みやすく、理解しやすいコードになります。この例では、ビルダーからPizzaオブジェクトにコピーされたパラメーターをチェックするようにビルドメソッドを変更、無効なパラメーター値が指定されている場合はIllegalStateExceptionスローできます。このパターンは柔軟性があり、将来、さらに多くのパラメーターを追加するのは簡単です。これは、コンストラクターに4つまたは5つを超えるパラメーターがある場合にのみ非常に役立ちます。とはいえ、将来さらにパラメータを追加する可能性があると思われる場合は、そもそも価値があるかもしれません

私はこのトピックについて、Joshua Bloch 著「Effective Java、2nd Edition」から大いに借りてきました。このパターンと他の効果的なJavaプラクティスについて詳しく知るために、私はそれを強くお勧めします。


24
元のGOFビルダーとは異なりますよね?部長クラスがないからです。私には別のパターンのように思えますが、それは非常に便利だと思います。
Lino Rosa、

194
この特定の例では、ブール値のパラメーターを削除して、次のように言ったほうがいいのではないでしょうかnew Pizza.Builder(12).cheese().pepperoni().bacon().build();
Fabian Steeg

46
これは、ビルダーパターンよりもFluent Interfaceに似ています。
ロバートハーベイ

21
@Fabian Steeg、私は人々がより見栄えの良いブールセッターに過剰反応していると思います、これらの種類のセッターはランタイムの変更を許可しないことに注意してください:Pizza.Builder(12).cheese().pepperoni().bacon().build();いくつかのペパロニだけが必要な場合は、コードを再コンパイルするか不要なロジックが必要になりますピザ。少なくとも、最初に提案された@Kamikaze Mercenaryのようなパラメータ化されたバージョンも提供する必要があります。Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();。繰り返しになりますが、ユニットテストは行いません。
egallardo 2013年

23
@JasonCそうです、そしてとにかく不変のピザはどのような用途ですか?
Maarten Bodewes 14

325

レストランを考えてみましょう。「今日の食事」の作成はファクトリーパターンです。なぜなら、キッチンに「今日の食事を入手して」と伝え、キッチン(工場)が非表示の基準に基づいて生成するオブジェクトを決定するためです。

カスタムピザを注文すると、ビルダーが表示されます。この場合、ウェイターはシェフ(ビルダー)に「ピザが必要です。それにチーズ、玉ねぎ、ベーコンを追加してください!」と伝えます。したがって、ビルダーは、生成されたオブジェクトに必要な属性を公開しますが、それらの設定方法を非表示にします。


ニティンはこの質問への別の答えでキッチンのアナロジーを拡張しました。
ポップス

19

.NET StringBuilderクラスはビルダーパターンの良い例です。主に一連のステップで文字列を作成するために使用されます。ToString()を実行して得られる最終結果は常に文字列ですが、その文字列の作成は、使用されたStringBuilderクラスの関数によって異なります。要約すると、基本的な考え方は、複雑なオブジェクトを構築し、構築方法の実装の詳細を隠すことです。


9
それがビルダーのパターンだとは思いません。StringBuilderは文字配列クラス(つまり文字列)のもう1つの実装ですが、文字列は不変であるため、パフォーマンスとメモリ管理が考慮されます。
Charles Graham、

23
これは、JavaのStringBuilderクラスと同様に、完全にビルダーパターンです。これらのクラスの両方のappend()メソッドがどのようにStringBuilder自体を返すかに注意してください。これにより、b.append(...).append(...)最後にを呼び出す前にチェーンできtoString()ます。引用: infoq.com/articles/internal-dsls-java
pohl

3
@pohlええ、それは本当にビルダーのパターンだとは思いません、それは単なる流暢なインターフェースだと思います。
Didier A.

「これらのクラスの両方のappend()メソッドがどのようにStringBuilder自体を返すかに注意してください」これはBuilderパターンではなく、単なる流暢なインターフェースです。ビルダーが流暢なインターフェイスを使用することもよくあります。Builderは流暢なインターフェースを持つ必要はありません。
bytedev

ただし、同期されるStringBufferとは異なり、本来StringBuilderは同期されないことに注意してください。
アランディープ

11

マルチスレッドの問題では、スレッドごとに構築する複雑なオブジェクトが必要でした。オブジェクトは処理中のデータを表し、ユーザー入力に応じて変化する可能性があります。

代わりに工場を使用できますか?はい

なぜしなかったのですか?Builderのほうが理にかなっていると思います。

ファクトリーは、同じ基本タイプである(同じインターフェースまたは基本クラスを実装する)異なるタイプのオブジェクトを作成するために使用されます。

ビルダーは同じタイプのオブジェクトを何度も構築しますが、構築は動的であるため、実行時に変更できます。


9

対処するオプションがたくさんあるときに使用します。jmockのようなものについて考えてください:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

それはずっと自然に感じられ、そして...可能です。

xmlの構築、文字列の構築、その他多くのものもあります。java.util.Mapビルダーとして置いていたら想像してみてください。あなたはこのようなことをすることができます:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);

3
「if」マップにビルダーパターンが実装されているのを読むのを忘れて、その構造を見て
驚いた

3
:)そのことについて申し訳ありません。多くの言語では、voidではなくselfを返すのが一般的です。Javaでもできますが、あまり一般的ではありません。
ダスティン

7
マップの例は、メソッドチェーンの単なる例です。
nogridbag 2013

@nogridbag実際には、メソッドのカスケードに近いでしょう。カスケードをシミュレートする方法でチェーンを使用しているため、明らかにチェーンですが、意味的にはカスケードとして動作します。
Didier A.

9

Microsoft MVCフレームワークを通過しているときに、ビルダーパターンについて考えました。ControllerBuilderクラスのパターンに遭遇しました。このクラスは、具体的なコントローラーを構築するために使用されるコントローラーファクトリクラスを返すためのものです。

私がビルダーパターンを使用する利点は、独自のファクトリを作成してフレームワークにプラグインできることです。

@Tetha、ピザを提供するイタリア人が経営するレストラン(Framework)があるかもしれません。ピザを準備するために、イタリア人の男(オブジェクトビルダー)はピザベース(基本クラス)でオーエン(ファクトリー)を使用します。

今度はインド人がイタリア人からレストランを引き継ぎます。インド料理レストラン(フレームワーク)は、ピザの代わりにドーササーバーを提供しています。dosaインド人(オブジェクトビルダー)を準備するために、フライダ(ファクトリー)をMaida(基本クラス)で使用します。

シナリオを見ると、料理は異なりますが、調理方法は異なりますが、同じレストラン(同じフレームワーク)で行われます。レストランは、中華料理、メキシコ料理、またはあらゆる料理をサポートできるように構築する必要があります。フレームワーク内のオブジェクトビルダーは、必要な料理のプラグインを容易にします。例えば

class RestaurantObjectBuilder
{
   IFactory _factory = new DefaultFoodFactory();

   //This can be used when you want to plugin the 
   public void SetFoodFactory(IFactory customFactory)
   {
        _factory = customFactory;
   }

   public IFactory GetFoodFactory()
   {
      return _factory;
   }
}

7

私は常に、ビルダーパターンを扱いにくい、目障りなものとして、そして経験の少ないプログラマーによって頻繁に悪用されるものとして嫌いでした。初期化後の手順を必要とするデータからオブジェクトをアセンブルする必要がある場合にのみ意味を持つパターン(つまり、すべてのデータが収集されたら、それで何かを行います)。代わりに、99%の時間でビルダーはクラスメンバーを初期化するためだけに使用されます。

そのような場合、単に宣言する方がはるかに良いです withXyz(...)、クラス内で型セッターをして、それ自体への参照を返すようにするています。

このことを考慮:

public class Complex {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first=first; 
    }

    ... 

    public Complex withFirst(String first){
       this.first=first;
       return this; 
    }

    public Complex withSecond(String second){
       this.second=second;
       return this; 
    }

    public Complex withThird(String third){
       this.third=third;
       return this; 
    }

}


Complex complex = new Complex()
     .withFirst("first value")
     .withSecond("second value")
     .withThird("third value");

これで、独自の初期化を管理し、ビルダーとほとんど同じ仕事をするきちんとした単一のクラスができました。


複雑なXMLドキュメントをJSONとして構築したいと思ったところです。まず、クラス 'Complex'が最初にXMLable製品を提供できることをどのようにして知ることができますか。また、JSONableオブジェクトを生成するためにどのように変更できますか?簡単な答え:ビルダーを使用する必要があるのでできません。そして、私たちは一周します...
デビッド・バーカー

1
合計bs、ビルダーは不変のオブジェクトを構築するように設計されており、製品クラスに触れずに将来の構築方法を変更できます
ミッキーティン

6
うーん?回答のどこかで、Builderがどのように設計されているかを読んだことがありますか?これは、「いつビルダーパターンを使用しますか?」の質問に対する別の見方です。これは、パターンの無数の乱用の経験に基づいています。すべてのパターンは、いつどのように使用するかがわかっている場合に役立ちます。それが、最初にパターンを文書化する重要なポイントです。パターンが過度に使用されたり悪化したりした場合-誤用された場合-は、コードのコンテキスト内でアンチパターンになります。ああ...
Pavel Lechev 2015

6

ビルダーのもう1つの利点は、ファクトリーがある場合でも、コードにいくつかのカップリングがあることです。ファクトリーが機能するためには、ファクトリーが作成できるすべてのオブジェクトを知っている必要があるためです。です。作成できる別のオブジェクトを追加する場合は、そのオブジェクトを含めるようにファクトリクラスを変更する必要があります。これは、抽象ファクトリでも発生します。

一方、ビルダーでは、この新しいクラスの新しい具象ビルダーを作成する必要があります。コンストラクターでビルダーを受け取るため、directorクラスは同じままです。

また、ビルダーにはさまざまなフレーバーがあります。カミカゼマーセナリーズがお届けします。


6
/// <summary>
/// Builder
/// </summary>
public interface IWebRequestBuilder
{
    IWebRequestBuilder BuildHost(string host);

    IWebRequestBuilder BuildPort(int port);

    IWebRequestBuilder BuildPath(string path);

    IWebRequestBuilder BuildQuery(string query);

    IWebRequestBuilder BuildScheme(string scheme);

    IWebRequestBuilder BuildTimeout(int timeout);

    WebRequest Build();
}

/// <summary>
/// ConcreteBuilder #1
/// </summary>
public class HttpWebRequestBuilder : IWebRequestBuilder
{
    private string _host;

    private string _path = string.Empty;

    private string _query = string.Empty;

    private string _scheme = "http";

    private int _port = 80;

    private int _timeout = -1;

    public IWebRequestBuilder BuildHost(string host)
    {
        _host = host;
        return this;
    }

    public IWebRequestBuilder BuildPort(int port)
    {
        _port = port;
        return this;
    }

    public IWebRequestBuilder BuildPath(string path)
    {
        _path = path;
        return this;
    }

    public IWebRequestBuilder BuildQuery(string query)
    {
        _query = query;
        return this;
    }

    public IWebRequestBuilder BuildScheme(string scheme)
    {
        _scheme = scheme;
        return this;
    }

    public IWebRequestBuilder BuildTimeout(int timeout)
    {
        _timeout = timeout;
        return this;
    }

    protected virtual void BeforeBuild(HttpWebRequest httpWebRequest) {
    }

    public WebRequest Build()
    {
        var uri = _scheme + "://" + _host + ":" + _port + "/" + _path + "?" + _query;

        var httpWebRequest = WebRequest.CreateHttp(uri);

        httpWebRequest.Timeout = _timeout;

        BeforeBuild(httpWebRequest);

        return httpWebRequest;
    }
}

/// <summary>
/// ConcreteBuilder #2
/// </summary>
public class ProxyHttpWebRequestBuilder : HttpWebRequestBuilder
{
    private string _proxy = null;

    public ProxyHttpWebRequestBuilder(string proxy)
    {
        _proxy = proxy;
    }

    protected override void BeforeBuild(HttpWebRequest httpWebRequest)
    {
        httpWebRequest.Proxy = new WebProxy(_proxy);
    }
}

/// <summary>
/// Director
/// </summary>
public class SearchRequest
{

    private IWebRequestBuilder _requestBuilder;

    public SearchRequest(IWebRequestBuilder requestBuilder)
    {
        _requestBuilder = requestBuilder;
    }

    public WebRequest Construct(string searchQuery)
    {
        return _requestBuilder
        .BuildHost("ajax.googleapis.com")
        .BuildPort(80)
        .BuildPath("ajax/services/search/web")
        .BuildQuery("v=1.0&q=" + HttpUtility.UrlEncode(searchQuery))
        .BuildScheme("http")
        .BuildTimeout(-1)
        .Build();
    }

    public string GetResults(string searchQuery) {
        var request = Construct(searchQuery);
        var resp = request.GetResponse();

        using (StreamReader stream = new StreamReader(resp.GetResponseStream()))
        {
            return stream.ReadToEnd();
        }
    }
}

class Program
{
    /// <summary>
    /// Inside both requests the same SearchRequest.Construct(string) method is used.
    /// But finally different HttpWebRequest objects are built.
    /// </summary>
    static void Main(string[] args)
    {
        var request1 = new SearchRequest(new HttpWebRequestBuilder());
        var results1 = request1.GetResults("IBM");
        Console.WriteLine(results1);

        var request2 = new SearchRequest(new ProxyHttpWebRequestBuilder("localhost:80"));
        var results2 = request2.GetResults("IBM");
        Console.WriteLine(results2);
    }
}

1
2つの方法で回答を改善できます。1)SSCCEにします。2)これが質問にどのように答えるかを説明します。
james.garriss 2015


3

私は自家製のメッセージングライブラリでビルダーを使用しました。ライブラリコアはワイヤーからデータを受け取り、ビルダーインスタンスで収集していました。ビルダーがメッセージインスタンスを作成するために必要なすべてのものを持っていると判断すると、ビルダーから取得したデータを使用してビルダーがメッセージインスタンスを作成していました。ワイヤー。


3

効果的なJavaで説明されているように、「ビルダー」アクションを「生成」メニューに追加するIntelliJ IDEAプラグインであるInnerBuilder(Alt + Insert)をチェックして、内部ビルダークラスを生成します。

https://github.com/analytically/innerbuilder


2

XMLで標準のXMLGregorianCalendarを使用してJavaのDateTimeのオブジェクトマーシャリングをオブジェクト化したいと思ったとき、それを使用するのがどれほど重くて扱いにくいかについて多くのコメントを聞きました。タイムゾーン、ミリ秒などを管理するために、xs:datetime構造体のXMLフィールドを制御しようとしていました。

そこで、GregorianCalendarまたはjava.util.DateからXMLGregorianカレンダーを作成するユーティリティを設計しました。

私が働いている場所のため、合法的にオンラインで共有することは許可されていませんが、クライアントがそれを使用する方法の例を次に示します。詳細を抽象化し、xs:datetimeではあまり使用されないXMLGregorianCalendarの実装の一部をフィルタリングします。

XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();

このパターンは、xmlCalendarのフィールドを未定義に設定して除外するため、よりフィルターのようなものですが、それでも「ビルド」されます。xs:dateおよびxs:time構造体を作成し、必要に応じてタイムゾーンオフセットを操作するために、ビルダーに他のオプションを簡単に追加しました。

XMLGregorianCalendarを作成して使用するコードを見たことがあれば、これによって操作がはるかに簡単になったことがわかります。


0

実例としては、クラスをユニットテストするときに使用します。sut(テスト対象システム)ビルダーを使用します。

例:

クラス:

public class CustomAuthenticationService
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationService(ICloudService cloudService, IDatabaseService databaseService)
    {
        _cloudService = cloudService;
        _databaseService = databaseService;
    }

    public bool IsAuthorized(User user)
    {            
        //Implementation Details
        return true;

}

テスト:

    [Test]
    public void Given_a_User_With_Permission_When_Verifying_If_Authorized_Then_Authorize_It_Returning_True()
    {
        CustomAuthenticationService sut = new CustomAuthenticationServiceBuilder();
        User userWithAuthorization = null;

        var result = sut.IsAuthorized(userWithAuthorization);

        Assert.That(result, Is.True);
    }

sut Builder:

public class CustomAuthenticationServiceBuilder
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationServiceBuilder()
    {
        _cloudService = new AwsService();
        _databaseService = new SqlServerService();
    }

    public CustomAuthenticationServiceBuilder WithAzureService(AzureService azureService)
    {
        _cloudService = azureService;

        return this;
    }

    public CustomAuthenticationServiceBuilder WithOracleService(OracleService oracleService)
    {
        _databaseService = oracleService;

        return this;
    }

    public CustomAuthenticationService Build()
    {
        return new CustomAuthenticationService(_cloudService, _databaseService);
    }

    public static implicit operator CustomAuthenticationService (CustomAuthenticationServiceBuilder builder)
    {
        return builder.Build();
    }
}

この場合、CustomAuthenticationServiceクラスにセッターを追加するだけでなく、ビルダーが必要なのはなぜですか。
Laur Ivan、2015

@LaurIvanは良い質問です。多分私の例は少し貧弱だったかもしれませんが、CustomAuthenticationServiceクラスを変更できないことを想像してみてください。ビルダーはユニットテストの読み取りを改善する魅力的な方法です。そして、ゲッターとセッターを作成するとフィールドが公開され、テストのためだけに使用されると思います。Sut Builderについてさらに詳しく知りたい場合は、Test Data Builderについて読むことができますが、SUTについてはほとんど同じです。
Rafael Miceli
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.