Builder パターンを使用する一般的な実世界の例は何ですか? それはあなたに何を買うのですか?Factoryパターンを使用しないのはなぜですか?
13 に答える
以下は、Java でのパターンとサンプル コードの使用を主張するいくつかの理由ですが、これはビルダー パターンの実装であり、デザイン パターンのギャング オブ フォーでカバーされています。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);
ここでの問題は、オブジェクトが複数の呼び出しで作成されるため、作成の途中で一貫性のない状態になる可能性があることです。これには、スレッドの安全性を確保するための多大な努力も必要です。
より良い代替手段は、Builder パターンを使用することです。
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 の setter メソッドは 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 プラクティスについて詳しく知るには、強くお勧めします。
レストランを考えてみましょう。「今日の食事」の作成はファクトリー パターンです。キッチンに「今日の食事をくれ」と伝えると、キッチン (ファクトリー) が隠された基準に基づいて、どのオブジェクトを生成するかを決定するからです。
カスタム ピザを注文すると、ビルダーが表示されます。この場合、ウェイターはシェフ (ビルダー) に「ピザが必要です。チーズ、タマネギ、ベーコンを追加してください!」と伝えます。したがって、ビルダーは、生成されたオブジェクトが持つべき属性を公開しますが、それらの設定方法を隠します。
ビルダーとファクトリ IMHO の主な違いは、オブジェクトをビルドするために多くのことを行う必要がある場合にビルダーが役立つことです。たとえば、DOM を想像してみてください。最終的なオブジェクトを取得するには、多数のノードとアトリビュートを作成する必要があります。ファクトリは、1 回のメソッド呼び出しでオブジェクト全体を簡単に作成できる場合に使用されます。
ビルダーを使用する 1 つの例は、XML ドキュメントの構築です。私は HTML フラグメントを構築するときにこのモデルを使用しました。 :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
このビルダーは、HTML を吐き出してくれます。これは、大規模な手続き型メソッドを説明するよりもはるかに読みやすいです。
.NET StringBuilderクラスは、ビルダーパターンの優れた例です。これは主に、一連の手順で文字列を作成するために使用されます。ToString()を実行すると最終的に得られる結果は常に文字列ですが、その文字列の作成は、StringBuilderクラスのどの関数が使用されたかによって異なります。要約すると、基本的な考え方は、複雑なオブジェクトを構築し、それがどのように構築されているかの実装の詳細を隠すことです。
Builder パターンは扱いにくく、目障りで、経験の浅いプログラマーによく悪用されるものとして、私はいつも嫌いでした。これは、初期化後の手順が必要なデータからオブジェクトを組み立てる必要がある場合にのみ意味のあるパターンです(つまり、すべてのデータが収集されたら、何かを行います)。代わりに、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");
これで、独自の初期化を管理し、はるかにエレガントであることを除けば、ビルダーとほとんど同じ仕事をするきちんとした単一のクラスができました。
マルチスレッドの問題では、スレッドごとに複雑なオブジェクトを構築する必要がありました。オブジェクトは処理中のデータを表し、ユーザー入力に応じて変化する可能性があります。
代わりに工場を使用できますか? はい
なぜそうしなかったのですか?ビルダーはもっと理にかなっていると思います。
ファクトリは、基本型が同じ (同じインターフェイスまたは基本クラスを実装する) さまざまな型のオブジェクトを作成するために使用されます。
ビルダーは同じタイプのオブジェクトを何度も構築しますが、構築は動的であるため、実行時に変更できます。
Microsoft MVC フレームワークを調べているうちに、ビルダー パターンについて考えました。ControllerBuilder クラスのパターンに出くわしました。このクラスは、具体的なコントローラーを構築するために使用されるコントローラー ファクトリ クラスを返します。
ビルダー パターンを使用する利点は、独自のファクトリを作成してフレームワークにプラグインできることです。
@Tetha、ピザを提供するイタリア人が経営するレストラン(フレームワーク)があるかもしれません。ピザを準備するために、イタリア人 (オブジェクト ビルダー) はピザ ベース (基本クラス) を持つ Owen (ファクトリー) を使用します。
現在、インド人がイタリア人からレストランを引き継いでいます。インド料理レストラン (Framework) は、ピザの代わりにドーサをサーバーに提供します。ドーサを準備するために、インドの男 (オブジェクト ビルダー) はマイダ (基本クラス) でフライパン (ファクトリー) を使用します。
シナリオを見ると、同じレストランで(同じ枠組みの下で)、食べ物が異なり、調理方法が異なります。レストランは、中華料理、メキシコ料理、またはあらゆる料理をサポートできるように構築する必要があります。フレームワーク内のオブジェクト ビルダーにより、必要な種類の料理を簡単にプラグインできます。例えば
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;
}
}
対処するオプションがたくさんある場合に使用します。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);
以前の回答 (しゃれた意図) に基づいて構築された優れた実例は、Groovyのビルトイン サポートですBuilders
。
Groovy ドキュメントのビルダーを参照してください
ビルダーのもう 1 つの利点は、Factory がある場合、コード内に何らかの結合が存在することです。これは、Factory が機能するためには、作成できる可能性のあるすべてのオブジェクトを認識している必要があるためです。作成できる別のオブジェクトを追加する場合は、ファクトリ クラスを変更してオブジェクトを含める必要があります。これは、Abstract Factory でも発生します。
一方、ビルダーを使用すると、この新しいクラス用に新しい具象ビルダーを作成するだけで済みます。コンストラクターでビルダーを受け取るため、ディレクター クラスは同じままです。
また、ビルダーには多くのフレーバーがあります。カミカゼ傭兵のは、別のものを与えます.
/// <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);
}
}
自家製のメッセージング ライブラリでビルダーを使用しました。ライブラリ コアはワイヤからデータを受信し、それを Builder インスタンスで収集し、Builder が Message インスタンスの作成に必要なものがすべて揃ったと判断すると、Builder.GetMessage() は、ワイヤー。
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 を作成して使用するコードを見たことがある場合は、これにより操作が非常に簡単になったことがわかります。