いくつかのどのようなものがあり、共通の、現実の世界の例 Builderパターンを使用したのは?何で買うの?なぜファクトリーパターンを使用しないのですか?
いくつかのどのようなものがあり、共通の、現実の世界の例 Builderパターンを使用したのは?何で買うの?なぜファクトリーパターンを使用しないのですか?
回答:
ビルダーとファクトリーIMHOの主な違いは、オブジェクトを構築するために多くのことを行う必要がある場合にビルダーが役立つことです。たとえば、DOMを想像してみてください。最終的なオブジェクトを取得するには、多くのノードと属性を作成する必要があります。ファクトリは、ファクトリが1つのメソッド呼び出しでオブジェクト全体を簡単に作成できる場合に使用されます。
ビルダーを使用する1つの例は、XMLドキュメントの構築です。HTMLフラグメントを構築するときにこのモデルを使用しました。たとえば、特定のタイプのテーブルを構築するためのビルダーがあり、次のメソッドがある場合があります(パラメーターは示していません)。:
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
このビルダーは、HTMLを出力してくれます。これは、大規模な手続き型メソッドを使用するよりもはるかに読みやすくなっています。
ウィキペディアでビルダーパターンを確認してください。
以下は、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プラクティスについて詳しく知るために、私はそれを強くお勧めします。
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
いくつかのペパロニだけが必要な場合は、コードを再コンパイルするか不要なロジックが必要になりますピザ。少なくとも、最初に提案された@Kamikaze Mercenaryのようなパラメータ化されたバージョンも提供する必要があります。Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
。繰り返しになりますが、ユニットテストは行いません。
レストランを考えてみましょう。「今日の食事」の作成はファクトリーパターンです。なぜなら、キッチンに「今日の食事を入手して」と伝え、キッチン(工場)が非表示の基準に基づいて生成するオブジェクトを決定するためです。
カスタムピザを注文すると、ビルダーが表示されます。この場合、ウェイターはシェフ(ビルダー)に「ピザが必要です。それにチーズ、玉ねぎ、ベーコンを追加してください!」と伝えます。したがって、ビルダーは、生成されたオブジェクトに必要な属性を公開しますが、それらの設定方法を非表示にします。
.NET StringBuilderクラスはビルダーパターンの良い例です。主に一連のステップで文字列を作成するために使用されます。ToString()を実行して得られる最終結果は常に文字列ですが、その文字列の作成は、使用されたStringBuilderクラスの関数によって異なります。要約すると、基本的な考え方は、複雑なオブジェクトを構築し、構築方法の実装の詳細を隠すことです。
b.append(...).append(...)
最後にを呼び出す前にチェーンできtoString()
ます。引用: infoq.com/articles/internal-dsls-java
マルチスレッドの問題では、スレッドごとに構築する複雑なオブジェクトが必要でした。オブジェクトは処理中のデータを表し、ユーザー入力に応じて変化する可能性があります。
代わりに工場を使用できますか?はい
なぜしなかったのですか?Builderのほうが理にかなっていると思います。
ファクトリーは、同じ基本タイプである(同じインターフェースまたは基本クラスを実装する)異なるタイプのオブジェクトを作成するために使用されます。
ビルダーは同じタイプのオブジェクトを何度も構築しますが、構築は動的であるため、実行時に変更できます。
対処するオプションがたくさんあるときに使用します。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);
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;
}
}
私は常に、ビルダーパターンを扱いにくい、目障りなものとして、そして経験の少ないプログラマーによって頻繁に悪用されるものとして嫌いでした。初期化後の手順を必要とするデータからオブジェクトをアセンブルする必要がある場合にのみ意味を持つパターン(つまり、すべてのデータが収集されたら、それで何かを行います)。代わりに、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");
これで、独自の初期化を管理し、ビルダーとほとんど同じ仕事をするきちんとした単一のクラスができました。
ビルダーのもう1つの利点は、ファクトリーがある場合でも、コードにいくつかのカップリングがあることです。ファクトリーが機能するためには、ファクトリーが作成できるすべてのオブジェクトを知っている必要があるためです。です。作成できる別のオブジェクトを追加する場合は、そのオブジェクトを含めるようにファクトリクラスを変更する必要があります。これは、抽象ファクトリでも発生します。
一方、ビルダーでは、この新しいクラスの新しい具象ビルダーを作成する必要があります。コンストラクターでビルダーを受け取るため、directorクラスは同じままです。
また、ビルダーにはさまざまなフレーバーがあります。カミカゼマーセナリーズがお届けします。
/// <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);
}
}
以前の答え(しゃれた意図)に基づいて構築された優れた実例は、Groovyの組み込みサポートですBuilders
。
Groovyドキュメントのビルダーを参照してください
効果的なJavaで説明されているように、「ビルダー」アクションを「生成」メニューに追加するIntelliJ IDEAプラグインであるInnerBuilder(Alt + Insert)をチェックして、内部ビルダークラスを生成します。
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を作成して使用するコードを見たことがあれば、これによって操作がはるかに簡単になったことがわかります。
実例としては、クラスをユニットテストするときに使用します。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
クラスにセッターを追加するだけでなく、ビルダーが必要なのはなぜですか。