web-dev-qa-db-ja.com

GoFのビルダーパターンの利点は何ですか?

更新:流暢なインターフェイスがなくても、ビルダーパターンを実行できます。 私の実装 を参照してください。

重複の可能性のある問題を編集します。

  • ビルダー設計パターンをいつ使用する必要がありますか? :私の質問は、ビルダーパターン(of GoF)の実際の利点についてです。そして、リンクで選択された回答はBloch Builderに関するものであり、@ amonの回答を参照)またはパターンではない可能性があります。

    デザインパターンは、特定の問題を解決するための[〜#〜] [〜#〜]ではありません[〜#〜](伸縮式コンストラクタなど)。参照を参照してください。

    では、何がパターンになるのでしょうか? (LHSはJohn Vlissidesによって指摘されたものです。RHSは私の意見です。)

    1. 再発。 (パターンは一般的なものであるべきなので、多くの問題に適用できます。)

    2. 教える。 (現在のソリューションシナリオを改善する方法を教えてくれます。)

    3. 名前があります。 (より効果的な会話のために。)

    参照:パターンハッチング: John Vlissides によって記述された、適用されたデザインパターン。第1章:覚えていれば、デザインパターンに関する一般的な誤解。

  • 実生活におけるビルダーのGoFの実装 :この回答は、ビルダーパターンに関する私のノートを読む前に読むことができます。それは素晴らしい答えですが、それでも私の質問を解決しません。そして、タイトルは関係ありません。


BuilderパターンのUML:

UML of Builder Pattern of GoF

リファレンス design-patterns-stories.com


私はビルダーパターンに関するGoFの本を何度も読んだことがあります。以下は私のメモの一部です。

  • ディレクター

    • ディレクターは再利用できます。
    • また、製品を段階的に構築するためのアルゴリズムも含まれています。
    • Builderが提供するインターフェースを使用します。
    • 論理的には、製品を作成します。
  • ビルダー

    • ビルダーは十分に一般的である必要があります(対応する製品を構築するために、ほとんどすべての可能なConcreteBuilderを許可します。これは、以下の2番目の質問の原点です)。
  • ConcreteBuilder

    • 具体的なビルダーは、必要なすべての部品を組み立て、それらを組み立てる方法を知っています。
    • そして、その製品を追跡します。 (製品のリファレンスが含まれています。)
    • クライアントは具体的なビルダーから最終製品を入手します。 (getProduct()メソッドを持つのはConcreteBuilderであり、BuilderにはgetProduct()(abstract)メソッドがない。)
  • 製品

    • 構築するのは複雑なオブジェクトです。 ConcreteBuilderごとに、対応するProductがあります。 (これが以下の最初の質問の原点です。)
    • そして、対応する具象ビルダーが論理パーツを作成してそれらを組み立てるためのインターフェースを提供します。

    これが、人々がBlochビルダーとGoFのビルダーパターンについて混乱した理由です。 Blochビルダーは、具象ビルダーがインターフェースを使いやすくするだけです。 (ところで、この行をインデントする方法...)

  • クライアント:

    • 彼が必要とする具体的なビルダーを選択してください。 (実施者)
    • そして、彼が必要とする監督を選びます。 (論理)
    • 次に、具体的なビルダーをディレクターに注入します。
    • ディレクターのconstruct()メソッドを呼び出します。
    • 具体的なビルダーのgetProduct()を呼び出して製品を取得します。

これらのルールをすべて覚えるのは大変ですが、いくつか質問があります。

最初:

Productが十分に複雑である場合、それは悪い設計になるはずです。

第二:

OK、悪いデザインではないと言うなら。では、Builderインターフェイスを設計して、ConcreteProductを満たすにはどうすればよいでしょうか。

以下は私にとってビルダーパターンの利点です

  • スコープ:AConcreteBuilderは、必要なすべてのコンポーネントを同じスコープで制約します。したがって、Builderのクライアントはsee製品

  • Builderのより少ないパラメーター:ConcreteBuilder内のすべてのメソッドはこれらの変数を共有できるため、ConcreteBuilderは読み書きが簡単です。 (本Clean Codeから、メソッドが持つパラメーターが多いほど悪くなります。)

  • 依存関係の逆転の原則Builderインターフェースはビルダーパターンで重要な役割を果たします。どうして?これは、Director(the logic)とConcreteBuilder(implementation)の両方が同じルールに従ってモノを構築するためです。

結局のところ、よくわかりません。実際の答えが必要です。


ビルダーパターンとは何かについて、さまざまな見方があります。人によって定義は異なりますが、ここではGoFのビルダーパターンについて説明します。よくお読みください。

以前は、@-Aaronの回答を いつビルダーパターンを使用しますか?[終了] でフォローし、それがGoFのビルダーパターンだと思いました。次に、CodeReviewに 実装方法 を投稿します。

しかし、そこの人々はそれがビルダーパターンではないと指摘しました。 @Robert Harveyのように、私はそれについて反対しました。だから私は本当の答えのためにここに来ます。

7
Niing

GoF Builderパターンは、Design Patternsブックで提案されているそれほど重要ではないパターンの1つです。パーサー、ドキュメントコンバーター、コンパイラー以外の純粋なGoF Builderパターンのアプリケーションを見たことがありません。ビルダーのようなAPIは、不変オブジェクトをアセンブルするためによく使用されますが、GoFが想定した互換性のある具体的なビルダーの柔軟性は無視されます。

「ビルダーパターン」という名前は、より一般的には、ジョシュアブロックのビルダーパターンに関連付けられています。これは、コンストラクターパラメーターが多すぎるという問題を回避することを目的としています。コンストラクタだけでなく他のメソッドにも適用される場合、これはMethod Object Patternとも呼ばれる手法です。

Go4 Builderパターンは次の場合に適用されます。

  • オブジェクトを1回の呼び出しで作成することはできません。これは、製品が不変である場合や、複雑なオブジェクトグラフが含まれている場合などです。循環参照あり。

  • さまざまな種類の製品をビルドする1つのビルドプロセスがあります。

後者の点は、GoFビルダーパターンの鍵です。コードは特定の製品に限定されず、抽象的なビルダー戦略の観点から機能します。 BuilderのUMLダイアグラムは戦略パターンに非常に似ていますが、Builderは非抽象メソッドを介してオブジェクトを作成するまでデータを蓄積するという違いがあります。例が役立つかもしれません。

「設計パターン」ブックのビルダーパターンの紹介例は、テキストコンバーターまたはドキュメントビルダーです。 (モデル内の)テキスト文書にはテキストが含まれ、テキストには異なるフォントを使用でき、テキストは段落区切りで区切ることができます。抽象ビルダーは、これらの機能を提供します。ユーザー(または「ディレクター」)は、この抽象ビルダーインターフェイスを使用してドキュメントを組み立てることができます。例えば。このテキスト

Lorem ipsum。

Dolor sit?

アメット。

のように作成されるかもしれません

_void CreateExampleText(ITextBuilder b)
{
  b.SwitchFont(Font.DEFAULT);
  b.AddText("Lorem ipsum.");
  b.AddParagraphBreak();

  b.SwitchFont(Font.BOLD);
  b.AddText("Dolor sit?");
  b.SwitchFont(Font.DEFAULT);
  b.AddParagraphBreak();

  b.SwitchFont(Font.ITALIC);
  b.AddText("Amet.");
}
_

ここで注目すべき点は、CreateExampleText()関数が特定のビルダーに依存せず、特定のドキュメントタイプを想定していないことです。したがって、プレーンテキスト、HTML、TeX、Markdown、またはその他の形式の具体的なビルダーを作成できます。もちろん、プレーンテキストビルダーは異なるフォントを表すことができないため、そのメソッドは空になります。

_interface ITextBuilder {
  void SwitchFont(Font f);
  void AddText(string s);
  void AddParagraphBreak();
}

class PlainTextBuilder : ITextBuilder
{
  private StringBuilder sb = new StringBuilder();

  void SwitchFont(Font f) { /* not possible in plain text */ }
  void AddText(string s) { sb.Append(s); }
  void AddParagraphBreak() { sb.AppendLine(); sb.AppendLine(); }

  string GetPlainText() { return sb.ToString(); }
}
_

次に、このdirector関数と具象ビルダーを使用できます

_var builder = new PlainTextBuilder();
CreateExampleText(builder);
var document = builder.GetPlainText();
_

StringBuilder自体もこの種のビルダーのように見えることに注意してください。完全な文字列(=製品)は、ユーザーコードによって少しずつ組み立てられます。ただし、C#StringBuilderはポリモーフィックではありません。抽象ビルダーはなく、具象ビルダーは1つだけです。そのため、GoFデザインパターンに完全には適合しません。 「StringBuilder」はビルダーデザインパターンのアプリケーションですか? への私の回答でこれについて詳しく説明します

Builderパターンの注目すべき実例は、 SAXストリーミングXMLパーサーのContentHandlerインターフェース です。このパーサーはドキュメントモデル自体を構築しませんが、ユーザー提供のコンテンツハンドラーを呼び出します。これにより、ユーザーがより単純なデータ表現しか必要としない場合に、完全なドキュメントモデルを構築するコストを回避できます。ユーザー提供のコンテンツハンドラーは必ずしもビルダーではありませんが、ユーザーは設計のドキュメントビルダーをSAXパーサーに挿入できます。

27
amon

ビルダーパターン は、非常に具体的な問題を解決します:Telescoping Constructors.

  public Food(int id, String name) {
    this(id, name, 0, 0, 0);
  }

  public Food(int id, String name, int calories) {
    this(id, name, calories, 0, 0);
  }

  public Food(int id, String name, int servingSize) {
    this(id, name, 0, servingSize, 0);
  }

  public Food(int id, String name, int fat) {
    this(id, name, 0, 0, fat);
  }

  public Food(int id, String name, int calories, int servingSize) {
    this(id, name, calories, servingSize, 0);
  }
  public Food(int id, String name, int calories, int fat) {
    this(id, name, calories, 0, fat);
  }

  public Food(int id, String name, int servingSize, int fat) {
    this(id, name, 0, servingSize, fat);
  }

  public Food(int id, String name, int calories, int servingSize, int fat) {
    this(id, name, calories, servingSize, fat);
  }

代わりにビルダーパターンを使用する場合は、次の操作を簡単に実行できます。

Food food = new FoodBuilder().SetName("Bananas").SetCalories(120).Build();

これはコンストラクターの改善にはあまり思えないかもしれませんが、8つのオプションパラメーターがあり、すべての便利な組み合わせを表現したい場合は、256のコンストラクターが必要になります。

また、上記のコンストラクタの例は実際には機能しません。servingSizefatは同じ型なので、メソッドのポリモーフィズムを適切に表す方法はありません。必須。

C#では、ビルダーパターンは必要ありません。C#はコンストラクターでデフォルト値を持つオプションのパラメーターをサポートしているため、コンストラクターは1つだけ必要です。

public Food(int id, string name, int calories=0, int servingSize=0, int fat=0)

デフォルト値が割り当てられているパラメーターはオプションです。

3
Robert Harvey

ビルダーパターンの主な利点:

私がそれを使いたくなったときはいつでも、それは私のクラスが多すぎる属性、特にオプションの属性を持っているのでおそらくうまく設計されていないことを思い出させ、分割する必要があります。

多くのオプション属性を持つクラスは、明確に定義されたエンティティを表していないため、そもそもクラスであってはなりません。これは、マップ、セット、またはその他のコンテナーとして実装する方が適切です。

3
Frank Puffer

私の理解では、最大の価値は、Build()ステップから得られる柔軟性です。

そのBuild()を使用すると、ほとんどの(多くの)言語でコンストラクタを使用して実行できない3つのことを実行できます。

  • nullを返す
  • 別のタイプを返す (うまくいけばサブタイプ!)
  • パラメータを早期に収集しますが、インスタンスの構築は延期します

そして、あなたは私が聞いた一つのことをすることができますが、コンストラクターがするのが嫌われる場合によっては1

  • 例外を投げる

ロバートの答え の注記にあるように、一部の言語では、コンストラクタの指数関数的な連作を免れることもできます。しかし、最大の値はBuild()ステップによって提供される柔軟性にあると私はまだ主張します。

そして、その利点の一部、特に遅延構築の一部は、ビルダーをある種の「構成子」に注入する機能です。

(私が実際にこれを活用した状況を思い出すのに苦労しています...しかし、私はそれを見つけたとき/見つけたら更新します!)


1.「コンストラクターが単独で致命的な例外をスローすることはありませんが、実行するコードが致命的な例外を引き起こす可能性があります。」 ( https://stackoverflow.com/a/77797/779572

0
svidgen