ビルダーパターンと継承
質問
オブジェクト階層があり、継承ツリーが深くなるにつれて複雑になっています。これらのどれも抽象的ではなく、したがって、それらのインスタンスはすべて、多かれ少なかれ洗練された目的を果たすものです。
パラメータの数が非常に多いので、いくつかのコンストラクタをコーディングするよりも、プロパティを設定するためにBuilderパターンを使用したいと思います。すべての組み合わせに対応する必要があるので、継承ツリーのリーフクラスは伸縮自在のコンストラクタを持つことになります。
私は設計中にいくつかの問題にぶつかったとき、ここで答えを探しました。まず、この問題を説明するために、簡単で浅い例を挙げましょう。
public class Rabbit
{
public String sex;
public String name;
public Rabbit(Builder builder)
{
sex = builder.sex;
name = builder.name;
}
public static class Builder
{
protected String sex;
protected String name;
public Builder() { }
public Builder sex(String sex)
{
this.sex = sex;
return this;
}
public Builder name(String name)
{
this.name = name;
return this;
}
public Rabbit build()
{
return new Rabbit(this);
}
}
}
public class Lop extends Rabbit
{
public float earLength;
public String furColour;
public Lop(LopBuilder builder)
{
super(builder);
this.earLength = builder.earLength;
this.furColour = builder.furColour;
}
public static class LopBuilder extends Rabbit.Builder
{
protected float earLength;
protected String furColour;
public LopBuilder() { }
public Builder earLength(float length)
{
this.earLength = length;
return this;
}
public Builder furColour(String colour)
{
this.furColour = colour;
return this;
}
public Lop build()
{
return new Lop(this);
}
}
}
さて、いくつかのコードができたので、画像化するために
Lop
:
Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
この呼び出しは、最後に連結された呼び出しが解決できないため、コンパイルされません。
Builder
メソッドを定義していない
earLength
. そのため、この方法ではすべての呼び出しを特定の順序で連結する必要があり、特に深い階層ツリーでは非常に非現実的です。
さて、答えを探している間に、私は以下のものに出会いました。 Java Builder クラスのサブクラス化 を使用することを示唆しています。 不思議なほど再帰的なジェネリックパターン . しかし、私の階層には抽象クラスがないため、この解決策は私には使えません。しかし、このアプローチは、機能するために抽象化とポリモーフィズムに依存しており、それが私のニーズに適応できるとは思えない理由です。
私が現在解決しているアプローチは、スーパークラスのすべてのメソッドをオーバーライドすることです。
Builder
のすべてのメソッドをオーバーライドして、単純に次のようにすることです。
public ConcreteBuilder someOverridenMethod(Object someParameter)
{
super(someParameter);
return this;
}
このアプローチで、私はチェーンコールを発行できるインスタンスを返されることを保証できます。これは Telescoping Anti-pattern ほどひどくはありませんが、2 番目に近いもので、私はこれを少し厄介なものだと考えています。
私が気づいていない、私の問題に対する別の解決策はありますか? できれば、デザイン パターンと一致する解決策をお願いします。ありがとうございます。
どのように解決するのですか?
これは確かに再帰的バインドで可能ですが、サブタイプビルダもジェネリックである必要があり、いくつかの中間抽象クラスが必要です。少し面倒ですが、それでも非ジェネリックバージョンよりは簡単です。
/**
* Extend this for Mammal subtype builders.
*/
abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> {
String sex;
String name;
B sex(String sex) {
this.sex = sex;
return self();
}
B name(String name) {
this.name = name;
return self();
}
abstract Mammal build();
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
}
/**
* Use this to actually build new Mammal instances.
*/
final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> {
@Override
Mammal build() {
return new Mammal(this);
}
}
/**
* Extend this for Rabbit subtype builders, e.g. LopBuilder.
*/
abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>>
extends GenericMammalBuilder<B> {
Color furColor;
B furColor(Color furColor) {
this.furColor = furColor;
return self();
}
@Override
abstract Rabbit build();
}
/**
* Use this to actually build new Rabbit instances.
*/
final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> {
@Override
Rabbit build() {
return new Rabbit(this);
}
}
葉っぱのクラスが "concrete"にならないようにする方法があります、ここで、もし、これがあったら。
class MammalBuilder<B extends MammalBuilder<B>> {
...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
extends MammalBuilder<B> {
...
}
次に、ダイヤモンドで新しいインスタンスを作成し、参照タイプにワイルドカードを使用する必要があります。
static RabbitBuilder<?> builder() {
return new RabbitBuilder<>();
}
これは、型変数に束縛されることで、例えば
RabbitBuilder
の全てのメソッドが
RabbitBuilder
を含む型を返します。
というのも、あらゆるところでワイルドカードを使用する必要があり、新しいインスタンスを作成するには、ダイアモンドまたは 生タイプ . いずれにせよ、少し厄介なことになりますね。
ちなみに、これについては
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
そのチェックされていないキャストを回避する方法があります。それは、メソッドを抽象化することです。
abstract B self();
そして、リーフのサブクラスでそれをオーバーライドします。
@Override
RabbitBuilder self() { return this; }
この方法の問題点は、より型安全ではあるものの、サブクラスが返せるのは
this
. 基本的に、どちらの方法でも、サブクラスは何か間違ったことをする機会があるので、私はそれらのアプローチのうちの1つを他よりも好む理由はあまり見当たりません。
関連
-
java マイクロソフト払い戻し予期せぬサーバーからのファイルの終了
-
配列定数は初期化子でのみ使用可能です。
-
Java appears タイプEを囲むインスタンスがアクセスできない。
-
Javaがリソースリークに遭遇した:'input'が閉じない 解決方法
-
WeChat小プログラム Bluetooth通信 Bluetoothモジュールデモ
-
[解決済み] JavaにおけるHashMapとHashtableの違いは何ですか?
-
[解決済み] serialVersionUIDとは何ですか、またなぜそれを使用する必要がありますか?
-
[解決済み] MVPとMVC、その違いは何ですか?
-
[解決済み] 静的クラスとシングルトンパターンの違い?
-
[解決済み] 私的相続、公的相続、保護相続の違いについて
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Eclipse問題 アクセス制限。タイプ 'SunJCE' が API でないことを解決し、/jdk ディレクトリにある /jre と jre の違いについて理解を深める。
-
executeQuery()でデータ操作文が発行できない。解決方法
-
Javaクラスが "Error occurred during initialization of boot layer "というエラーで実行される。
-
JAVA_HOME環境変数が正しく定義されていない問題を解決する
-
名前 'XXX' を持つ Bean の作成に失敗しました。自動依存関係の注入に失敗しました 解決方法
-
BindException: アドレスはすでに使用中です:バインドエラー解決
-
SocketException java.netの4つの例外解決策。
-
eclipse にリソースリーク:'in' が閉じない
-
代入の左辺は変数でなければならない 解答
-
[解決済み] Java Builder クラスのサブクラス化