web-dev-qa-db-ja.com

いつビルダーパターンを使用しますか。

Builderパターンを使用した一般的な現実の例は何ですか?それはあなたに何を買うのですか?なぜファクトリーパターンを使わないのですか?

488
Charles Graham

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

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

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

このビルダーは、HTMLを吐き出します。これは、大規模な手順を踏むよりも読みやすくなっています。

WikipediaのBuilderパターン をご覧ください。

248
JoshBerke

以下は、Javaでのパターンとサンプルコードの使用を主張するいくつかの理由ですが、Design PatternsのGang of FourでカバーされているBuilderパターンの実装です。 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パラメーター長になると、覚えにくいパラメータの必須の順序、および特定の状況で必要になる可能性のある特定のコンストラクタ。

テレスコープコンストラクターパターンに必要な alternative は、 JavaBean Pattern です。ここでは、必須パラメーターを使用してコンストラクターを呼び出し、オプションのセッターを呼び出します。後:

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は不変であり、パラメーター値はすべて単一の場所にあることに注意してください。 BuilderのセッターメソッドはBuilderオブジェクトを返すため、チェーンにすることができます。

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

これにより、コードの記述が容易になり、読み取りと理解が非常に容易になります。この例では、 buildメソッドを変更できますビルダーからPizzaオブジェクトにコピーされた後にパラメーターをチェックし、無効なパラメーター値が指定された場合は IllegalStateExceptionをスローします。このパターンは柔軟で、パラメーターを簡単に追加できます。将来的には。コンストラクターに4つまたは5つ以上のパラメーターを使用する場合にのみ、本当に役立ちます。とはいえ、そもそも価値があるかもしれません将来さらにパラメータを追加するかもしれないと疑われる場合/

Joshua Blochの本Effective Java、2nd Editionからこのトピックを大いに借りました。このパターンと他の効果的なJavaプラクティスの詳細については、をお勧めします。

975
Aaron

レストランを考えてみましょう。 「今日の食事」の作成は工場のパターンです。なぜなら、あなたは台所に「今日の食事を入手してください」と伝え、台所(工場)は隠れた基準に基づいて生成するオブジェクトを決定するからです。

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

309
Tetha

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

18
dssfsdf

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

代わりに工場を使うことができますか?はい

どうして私達はしなかったの?ビルダーはもっと理にかなっていると思います。

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

ビルダーは同じタイプのオブジェクトを繰り返し構築しますが、構築は動的なので実行時に変更できます。

10

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

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

@Tetha、イタリア人が運営するレストラン(Framework)があり、ピザを提供しています。ピザを準備するために、イタリア人(Object Builder)は、Owen(Factory)をピザの基本(基本クラス)と共に使用します。

今インド人はイタリア人からレストランを引き継ぎます。ピザの代わりにインド料理店(Framework)サーバーdosa。 dosaインド人(オブジェクトビルダー)を準備するために、Maida(基本クラス)と共にFrying Pan(Factory)を使用します

シナリオを見ると、食べ物が違う、食べ物が用意される方法が異なりますが、同じレストランで(同じフレームワークの下で)。レストランは中華料理、メキシコ料理、その他の料理をサポートできるように作られているべきです。フレームワーク内のオブジェクトビルダーは、あなたが望む種類の料理をプラグインするのを容易にします。例えば

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;
   }
}
9
Nitin

対処するオプションがたくさんあるときに使用します。 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);
8
Dustin

これまでの答えを基にして(意図的に)、実世界での優れた例は Groovy の組み込みサポートBuildersname__です。

Groovy DocumentationBuilders を参照してください

6
Ken Gentle

私はいつもビルダーのパターンを扱いにくく、目障りで、経験の浅いプログラマーによって頻繁に悪用されるものとして嫌いました。初期化後のステップを必要とするデータからオブジェクトを組み立てる必要がある場合にのみ意味のあるパターンです。それ)。代わりに、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");

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

5
Pavel Lechev
/// <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);
    }
}
5
Raman Zhylich

ビルダーのもう1つの利点は、ファクトリーがある場合でも、ファクトリーが機能するためには、作成可能なすべてのオブジェクトを知っている必要があるため 。作成可能な別のオブジェクトを追加した場合は、それを含むようにファクトリクラスを修正する必要があります。これは抽象ファクトリーでも起こります。

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

また、ビルダーの風味がたくさんあります。神風傭兵が別のものをくれる。

5
Lino Rosa

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

3
wasker

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を作成および使用するコードを見たことがあれば、これによって操作がはるかに簡単になったことがわかります。

2
John Brown

IntelliJ IDEAプラグイン、InnerBuilderをチェックしてください。これは、Generateメニュー(Alt + Insert)に 'Builder'アクションを追加したものです。

https://github.com/analytically/innerbuilder

2
analytically

実世界の良い例は、クラスを単体テストするときに使用することです。あなたはsut(System Under Test)ビルダーを使います。

例:

クラス:

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();
    }
}
0
Rafael Miceli