web-dev-qa-db-ja.com

型がビルダーと結合されるのはなぜですか?

Code Review にある Java の私の回答を最近削除しました。

private Person(PersonBuilder builder) {

やめる。赤旗。 PersonBuilderはPersonを作成します。それは人について知っています。 Personクラスは、PersonBuilderについて何も認識すべきではありません。これは単なる不変の型です。ここで、AがAに依存するBに依存する循環結合を作成しました。

その人はそのパラメータを取り入れるべきです。構築せずにPersonを作成する意思があるクライアントは、それを実行できるはずです。

私は反対票を叩かれ、それを(引用) Red flagと言われました、なぜですか?ここでの実装は、Joshua Blochが彼の「Effective Java」の本(item#2)で示したものと同じ形をしています。

したがって、Javaで Builderパターンを実装する1つの正しい方法は、 ビルダーをネストされたタイプにすることです (これはこの質問についてではありません)、次に product (ビルドされるオブジェクトのクラス)を作成します次のように、ビルダーに依存します。

private StreetMap(Builder builder) {
    // Required parameters
    Origin      = builder.Origin;
    destination = builder.destination;

    // Optional parameters
    waterColor         = builder.waterColor;
    landColor          = builder.landColor;
    highTrafficColor   = builder.highTrafficColor;
    mediumTrafficColor = builder.mediumTrafficColor;
    lowTrafficColor    = builder.lowTrafficColor;
}

https://en.wikipedia.org/wiki/Builder_pattern#Java_example

同じビルダーパターンの同じWikipediaページには、C#の実装が大きく異なります(はるかに柔軟です)。

//Represents a product created by the builder
public class Car
{
    public Car()
    {
    }

    public int Wheels { get; set; }

    public string Colour { get; set; }
}

ご覧のとおり、ここの product Builderクラスについて何も認識していません。また、直接コンストラクター呼び出し、工場、...またはビルダー-私が理解している限り、作成パターンの product neverそれを作成しているものについて何かを知る必要があります。

ビルダーパターンを使用して、数十のオプションの引数で膨らんだコンストラクターを持つ型を作り直すことができるという反論(Blochの本で明らかに明示的に保護されています)を受けました。したがって、私が thought にこだわるのではなく、このサイトで少し調査したことを知って、私が思ったように、 この引数は保持されないことがわかりました水

それで、契約は何ですか?そもそも存在すべきではない問題に対して、なぜ over-engineered solution を考え出すのですか?ジョシュア・ブロッホを台座から1分間外した場合、2つの具象型を結合し、それをベストプラクティスと呼ぶ、正当で正当な理由が1つ考えられますか。

これは、 cargo-cult programming のすべての悪意です。

21
Mathieu Guindon

それが危険信号であるというあなたの主張に同意しません。私はまた、ビルダーパターンを表現する1つの正しい方法があるという反対点の表現に同意しません。

私には1つの質問があります:ビルダーはタイプのAPIの必要な部分ですか?ここで、PersonBuilderはPerson APIの必要な部分ですか?

妥当性チェックされた、不変の、または厳密にカプセル化されたPersonクラスが必要に応じて作成されることは完全に合理的です(=そのビルダーがネストされているか隣接しているかに関係なく)。そうすることで、Personはすべてのフィールドをプライベートまたはパッケージプライベートでファイナルに保つことができます。また、進行中の作業であれば、クラスを変更用に開いたままにできます。 (それはend up変更のために閉じられ、拡張のために開かれるかもしれませんが、それは今のところ重要ではなく、不変のデータオブジェクトについては特に議論の余地があります。)

Personが、単一のパッケージレベルのAPI設計の一部として、提供されたPersonBuilderを介して必ず作成される場合は、循環結合で十分であり、ここでは赤いフラグは必要ありません。特に、APIの設計に関する意見や選択ではなく、議論の余地のない事実であると述べたため、不適切な応答の原因となった可能性があります。私はあなたに反対票を投じなかったでしょうが、そうしたことについて他の誰かを責めることはありません。「反対票を正当化するもの」についての残りの議論は Code Reviewヘルプセンター に任せます。および meta 。反対票が発生します。それは「平手打ち」ではありません。

もちろん、Personオブジェクトを構築するために多くの方法を開いたままにしたい場合、PersonBuilderはPersonクラスのconsumerまたはtilityになることができます。人は直接依存すべきではありません。この選択はより柔軟に見えます—より多くのオブジェクト作成オプションを必要としない人はいますか?—しかし、保証(不変性または直列化可能性など)を守るために、Personは非常に長いコンストラクターなどの異なる種類のパブリック作成手法を公開する必要があります。 Builderが、コンストラクターを多数のオプションフィールドまたはコンストラクターで変更できるように表現または置換することを意図している場合、クラスの長いコンストラクターは、実装の詳細を隠したほうがよい場合があります。 (また、公式で必要なビルダーは、独自の構築ユーティリティを作成することを妨げるものではなく、上記の元の質問のように、構築ユーティリティがビルダーをAPIとして使用する可能性があることにも注意してください。)


追記:リストしたコードレビューサンプルには不変のPersonが含まれていますが、ウィキペディアの反例には、ゲッターとセッターを備えた変更可能なCarがリストされています。言語と不変条件の一貫性を保つと、必要な機械が車から省かれているのがわかりやすくなります。

22
Jeff Bowman

「権威へのアピール」が討論で使用されるとき、それがいかにいらいらすることができるかを理解しています。議論は独自のIMOに基づくべきであり、そのように尊敬されている人の助言を指摘するのは間違いではありませんが、アリストテレスがそう言っているため、太陽が地球を旅していることを知っているなら、それ自体を完全な議論と見なすことはできません。 。

そうは言っても、あなたの議論の前提は同じようなものだと思います。 2つの具象タイプを結合して、それをベストプラクティスと呼ぶべきではありません。

カップリングに問題があるという議論を有効にするには、それが生み出す特定の問題が必要です。このアプローチがこれらの問題の影響を受けない場合、この形式の結合が問題であると論理的に論じることはできません。ですから、ここであなたの指摘をしたいのであれば、このアプローチがこれらの問題にどのように影響を受けるか、および/またはビルダーを分離することでデザインがどのように改善されるかを示す必要があると思います。

オフハンドで、私は結合されたアプローチがどのように問題を引き起こすかを見るのに苦労しています。クラスは完全に結合されていますが、ビルダーはクラスのインスタンスを作成するためだけに存在します。これを見る別の方法は、コンストラクターの拡張としてです。

7
JimmyJames

型がビルダーと結合される理由に答えるには、そもそもなぜ誰もがビルダーを使用する理由を理解する必要があります。特に:コンストラクターパラメーターが多数ある場合は、Blochはビルダーの使用をお勧めします。それがビルダーを使用する唯一の理由ではありませんが、私はそれが最も一般的であると推測しています。つまり、ビルダーはこれらのコンストラクターパラメーターの代わりであり、多くの場合、ビルダーはビルドしているのと同じクラスで宣言されます。したがって、囲んでいるクラスはビルダーの知識をすでに持っており、コンストラクター引数としてそれを渡してもそれは変わりません。これが、型がそのビルダーと結合される理由です。

多数のパラメーターがあると、ビルダーを使用する必要があることを意味するのはなぜですか?まあ、多くのパラメータを持つことは現実の世界では珍しいことではなく、単一責任の原則に違反することもありません。また、10個の文字列パラメータがあり、どれがどれであるか、およびnullにする必要があるのが5番目または6番目の文字列であるかどうかを思い出そうとしているときにも問題が発生します。ビルダーを使用すると、パラメーターの管理が容易になります。また、本を読んで調べる必要のある他のすばらしいことも実行できます。

タイプと組み合わせられていないビルダーをいつ使用しますか?一般に、オブジェクトのコンポーネントがすぐに利用できず、それらのピースを含むオブジェクトが、構築しようとしている型を認識していない場合、または制御できないクラスのビルダーを追加している場合。

4
Old Fat Ned

創造的なパターンの製品は、それを作成しているものについて何も知る必要はありません。

Personクラスはそれを作成しているものを認識していません。これにはパラメーターを持つコンストラクターしかないので、何ですか?パラメータのタイプの名前は... Builderで終わりますが、これは実際には何も強制しません。結局のところそれは単なる名前です。

ビルダーは賞賛されています1 構成オブジェクト。そして、構成オブジェクトは実際には、互いに属しているプロパティをグループ化するだけです。それらがクラスのコンストラクターに渡される目的でのみ互いに​​属している場合、そうなります! Originの例のdestinationStreetMapはどちらもPointタイプです。各ポイントの個別の座標をマップに渡すことはできますか?もちろん、Pointのプロパティは一緒に属しており、さらにそれらの構築に役立つあらゆる種類のメソッドを持つことができます。

1違いは、ビルダーは単なるデータオブジェクトではなく、これらのチェーンされたセッター呼び出しを可能にすることです。 Builderは、主にそれ自体を構築する方法に関係しています。

ここで、コマンドパターンを少し追加してみましょう。よく見ると、ビルダーは実際にはコマンドです。

  1. アクションをカプセル化します:別のクラスのメソッドを呼び出す
  2. そのアクションを実行するために必要な情報を保持します:メソッドのパラメーターの値

ビルダーの特別な部分は次のとおりです。

  1. 呼び出されるメソッドは、実際にはクラスのコンストラクタです。 創造的なパターンと呼んでください。
  2. パラメータの値はビルダー自体です

Personコンストラクターに渡されるものはそれを構築できますが、必ずしもそうである必要はありません。プレーンな古い構成オブジェクトまたはビルダーの可能性があります。

1
null

いいえ、それらは完全に間違っており、あなたは完全に正しいです。そのビルダーモデルはばかげています。コンストラクターの引数の由来はPersonのビジネスではありません。ビルダーはユーザーにとって便利なものにすぎません。

0
DeadMG