Builderパターンはいつ使用しますか? [閉まっている]
-
11-07-2019 - |
質問
Builderパターンを使用した一般的な、実世界の例は何ですか?それは何を買いますか?ファクトリーパターンを使用しないのはなぜですか?
解決
ビルダーとファクトリーIMHOの主な違いは、オブジェクトを作成するために多くのことを行う必要がある場合にビルダーが役立つことです。たとえば、DOMを想像してください。最終的なオブジェクトを取得するには、多くのノードと属性を作成する必要があります。ファクトリは、1回のメソッド呼び出しでオブジェクト全体を簡単に作成できる場合に使用されます。
ビルダーを使用する1つの例は、XMLドキュメントの作成です。HTMLフラグメントを作成するときにこのモデルを使用しました。たとえば、特定のタイプのテーブルを作成するBuilderがあり、次のメソッドがある場合があります (パラメーターは表示されません):
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
このビルダーは、HTMLを吐き出します。これは、大規模な手順を踏むよりも読みやすくなっています。
Wikipediaのビルダーパターンをご覧ください。
他のヒント
以下は、Javaでのパターンとサンプルコードの使用を主張するいくつかの理由ですが、デザインパターンの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つのパラメーターの長さになると、必要なを覚えにくいになることです。パラメータの順序、および特定の状況で必要になる可能性のある特定のコンストラクタ。
Telescoping Constructor Patternに必要な 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は不変であり、パラメーター値はすべて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プラクティスの詳細については、強くお勧めします
レストランを検討してください。 「今日の食事」の作成キッチンに「今日の食事をゲットして」と言うので、工場パターンです。キッチン(工場)は、非表示の基準に基づいて、生成するオブジェクトを決定します。
カスタムピザを注文すると、ビルダーが表示されます。この場合、ウェイターはシェフ(ビルダー)"ピザが必要です;チーズ、玉ねぎ、ベーコンを追加してください!」したがって、ビルダーは生成されたオブジェクトが持つべき属性を公開しますが、それらの設定方法を隠します。
.NET StringBuilderクラスは、ビルダーパターンの優れた例です。主に、一連の手順で文字列を作成するために使用されます。 ToString()の実行で得られる最終結果は常に文字列ですが、その文字列の作成は、StringBuilderクラスのどの関数が使用されたかによって異なります。要約すると、基本的な考え方は、複雑なオブジェクトを構築し、その構築方法の実装の詳細を隠すことです。
マルチスレッドの問題では、スレッドごとに複雑なオブジェクトを構築する必要がありました。オブジェクトは処理中のデータを表し、ユーザー入力に応じて変化する可能性があります。
代わりにファクトリを使用できますか?はい
なぜそうしなかったのですか? Builderはもっと理にかなっていると思います。
Factoriesは、同じ基本タイプ(同じインターフェースまたは基本クラスを実装)の異なるタイプのオブジェクトを作成するために使用されます。
ビルダーは同じタイプのオブジェクトを何度も構築しますが、構築は動的であるため、実行時に変更できます。
Microsoft MVCフレームワークを使用しながら、ビルダーパターンについて考えました。 ControllerBuilderクラスのパターンに出会いました。このクラスは、コントローラファクトリクラスを返します。このクラスは、具体的なコントローラの構築に使用されます。
ビルダーパターンを使用する利点は、独自のファクトリを作成してフレームワークにプラグインできることです。
@Tetha、イタリア人が経営するレストラン(フレームワーク)があり、ピザを提供しています。ピザを準備するために、イタリア人の男(オブジェクトビルダー)は、ピザベース(ベースクラス)を持つオーウェン(工場)を使用します。
今、インド人がイタリア人からレストランを引き継いでいます。ピザの代わりにインドのレストラン(フレームワーク)サーバーdosa。 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;
}
}
対処するオプションがたくさんある場合に使用します。 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の
MarkupBuilder
<を使用したXMLの作成/ li> - Groovyの
StreamingMarkupBuilder
<を使用したXMLの作成/ li> - Swing Builder
-
SwingXBuilder
Builderのもう1つの利点は、ファクトリがある場合、コード内にまだいくつかのカップリングが存在することです。ファクトリが機能するためには、作成できる可能性のあるすべてのオブジェクトを知る必要があるため 。作成できる別のオブジェクトを追加する場合は、ファクトリクラスを変更してそのオブジェクトを含める必要があります。これは、抽象ファクトリでも発生します。
一方、ビルダーでは、この新しいクラス用の新しい具象ビルダーを作成するだけです。ディレクタークラスは、コンストラクターでビルダーを受け取るため、同じままです。
また、ビルダーには多くのフレーバーがあります。カミカゼ兵は別のものを与えます。
/// <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);
}
}
私は、ビルダーパターンが扱いにくい、目障りで、経験の浅いプログラマーによって頻繁に悪用されるものとして常に嫌いでした。そのパターンは、初期化後のステップを必要とするデータからオブジェクトをアセンブルする必要がある場合にのみ意味があります(つまり、すべてのデータが収集されたら-それで何かを行います)。代わりに、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");
これで、独自の初期化を管理し、はるかにエレガントであることを除いて、ビルダーとほぼ同じ仕事をするきちんとした単一のクラスができました。
自家製のメッセージングライブラリでビルダーを使用しました。ライブラリコアはワイヤーからデータを受信し、Builderインスタンスで収集し、BuilderがMessageインスタンスを作成するために必要なものをすべて持っていると判断すると、Builder.GetMessage()は、ワイヤー。
JavaでDateTimeのオブジェクトマーシャリングを行うXMLに標準のXMLGregorianCalendarを使用したいとき、それを使用することの重さと面倒さについて多くのコメントを聞きました。タイムゾーン、ミリ秒などを管理するために、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を作成および使用するコードを見たことがあるなら、これにより操作がはるかに簡単になったことがわかります。
「InnerBuilder」を確認してください。IntelliJIDEAプラグインは、「ビルダー」アクションを「生成」メニュー(Alt + Insert)に追加します。
実世界での素晴らしい例は、クラスを単体テストするときに使用することです。 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();
}
}