3

抽象クラス (BaseThing) があるとします。これには、1 つの必須パラメーター ("base required") と 1 つのオプション パラメーター ("base optional") があります。それを拡張する具象クラスがあります(モノ)。また、1 つの必須パラメーター (「required」) と 1 つの省略可能なパラメーター (「optional」) もあります。次のようなものです:

public abstract class BaseThing {
    public static final String DEFAULT_BASE_OPTIONAL = "Default Base Optional";

    private final String baseRequired;
    private String baseOptional = DEFAULT_BASE_OPTIONAL;

    protected BaseThing(final String theBaseRequired) {
        this.baseRequired = theBaseRequired;
    }

    final void setBaseOptional(final String newVal) {
        this.baseOptional = newVal;
    }

    public final void selfDescribe() {
        System.out.println("Base Required: " + baseRequired);
        System.out.println("Base Optional: " + baseOptional);

        selfDescribeHook();
    }

    protected abstract void selfDescribeHook();
}

と:

public final class Thing extends BaseThing {
    public static final String DEFAULT_OPTIONAL = "Default Optional";

private final String required;
    private String optional = DEFAULT_OPTIONAL;

    Thing(final String theRequired, final String theBaseRequired) {
        super(theBaseRequired);
        required = theRequired;
    }

    @Override
    protected void selfDescribeHook() {
        System.out.println("Required: " + required);
        System.out.println("Optional: " + optional);
    }

    void setOptional(final String newVal) {
        optional = newVal;
    }
}

Thing オブジェクト用の Joshua Bloch スタイルのビルダーが必要です。しかし、より一般的には、BaseThing の具体的な実装にビルダーを簡単に実装できるようにしたいので、私が本当に必要としている (私が思うに) のは、ThingBuilder、OtherThingBuilder、または SuperThingBuilder を簡単に作成できる BaseThing ビルダーです。 .

私が思いついた次の方法よりも良い方法はありますか (または、私が思いついたものに問題がありますか)?

public abstract class BaseThingBuilder<T extends BaseThing> {
    private String baseOptional = BaseThing.DEFAULT_BASE_OPTIONAL;

    public BaseThingBuilder<T> setBaseOptional(final String value) {
        baseOptional = value;
        return this;
    }

    public T build() {
        T t = buildHook();
        t.setBaseOptional(baseOptional);

        return t;
    }

    protected abstract T buildHook();
}

と:

public final class ThingBuilder extends BaseThingBuilder<Thing> {
    private final String baseRequired;
    private final String required;
    private String optional = Thing.DEFAULT_OPTIONAL;

    public ThingBuilder(final String theRequired,
            final String theBaseRequired) {
        required = theRequired;
        baseRequired = theBaseRequired;
    }

    public ThingBuilder setOptional(final String value) {
        optional = value;
        return this;
    }

    protected Thing buildHook() {
        Thing thing = new Thing(required, baseRequired);
        thing.setOptional(optional);

        return thing;
    }
}

これは、次のような方法で Thing オブジェクトを構築するために使用できます。

        BaseThingBuilder<Thing> builder = 
                new ThingBuilder("Required!", "Base Required!")
                    .setOptional("Optional!")
                    .setBaseOptional("Base Optional!");
        Thing thing = builder.build();
        thing.selfDescribe();

どの出力:

Base Required: Base Required!
Base Optional: Base Optional!
Required: Required!
Optional: Optional!

私が知っているが、特に重要とは考えていない問題の 1 つは (改善できるのであればそうするのが良いでしょう)、基本オプションを設定する前に、基本以外のすべてのオプションを設定する必要があることです。そうしないと、setBaseOptional() が ThingBuilder ではなく BaseThingBuilder を返すため、構文エラーが発生します。

前もって感謝します。

4

2 に答える 2

3

ビルダーをそのように考えるのは良い考えではないと思います。ビルダーの階層は、通常、頭痛の種や壊れやすいコードにつながります。

具体的なビルダーで記述する必要があるコードの量を削減し、ベース ビルダーのロジックを再利用することは、ドメインと密接に結びついています。一般的なソリューションを開発することは容易ではありません。とにかく、例を見てみましょう。

public interface Builder<T> {
  T build();
}

public class Person {
  private final String name;

  //the proper way to use a builder is to pass an instance of one to
  //the class that is created using it...
  Person(PersonBuilder builder) {
    this.name = builder.name;
  }

  public String getName(){ return name; }

  public static class PersonBuilder implements Builder<Person> {
    private String name;
    public PersonBuilder name(String name){ this.name = name; return this; }

    public Person build() {
      if(name == null) {
        throw new IllegalArgumentException("Name must be specified");
      }
      return new Person(this);
    }
  }
}

グルーヴィー、ベイビー!それで?生徒を表すクラスを追加したいと思うかもしれません。職業はなんですか?Personを拡張しますか?確かに、それは有効です。もっと「奇妙な」ルートをたどって、集約を試みてみませんか? ええ、それもできます...あなたの選択は、最終的にビルダーを実装する方法に影響を与えます。従来のパスに固執して Person を拡張するとしましょう (すでに自問し始めているはずPersonです。具象クラスにすることは理にかなっていますか? 抽象化する場合、本当にビルダーが必要ですか? クラスが抽象化されている場合、ビルダーは抽象的ですか?):

public class Student extends Person {
  private final long id;

  Student(StudentBulder builder) {
    super(builder);
    this.id = builder.id;
  }

  public long getId(){ return id; }

  //no need for generics, this will work:
  public static class StudentBuilder extends PersonBuilder {
    private long id;
    public StudentBuilder id(long id){ this.id = id; return this; }

    public Student build() {
      if(id <= 0) {
        throw new IllegalArgumentException("ID must be specified");
      }
      return new Student(this);
    }
  }
}

わかりました、これはあなたが望んでいたものとまったく同じように見えます! だから、あなたはそれを試してください:

Person p = new PersonBuilder().name("John Doe").build();
Student s = new StudentBuilder().name("Jane Doe").id(165).build();

素晴らしく見える!ただし、コンパイルしません... 2行目にエラーがあり、記載されていますThe method id(int) is undefined for the type Person.PersonBuilder。問題はPersonBuilder#name、タイプのビルダーを返すPersonBuilderことです。これはあなたが望むものではありません。StudentBuilderあなたの中で、実際には戻りタイプのものを望んでいnameますStudentBuilder。さて、あなたは事前に考えて、何かが延長されれば、StudentBuilderそれが完全に何かを返したいと思うだろうと気づきます...それは実行可能ですか?はい、ジェネリックで。しかし、それは地獄のように醜く、かなりの複雑さを紹介します。したがって、私はそれを説明するコードを投稿することを拒否します。誰かがこのスレッドを見て、実際にソフトウェアで使用することを恐れているからです。

メソッド呼び出しの再配置 ( を呼び出すid前に呼び出すname):が機能すると思うかもしれませんnew StudentBuilder().id(165).name("Jane Doe").build()が、そうではありません。Student少なくとも:への明示的なキャストがないわけではありません。(Student)new StudentBuilder().id(165).name("Jane Doe").build()なぜなら、この場合、PersonBuilder#build戻り値の型がPerson...である が呼び出されているからです。明示的なキャストなしで機能したとしても、ビルダーのメソッドを特定の順序で呼び出す必要があることを知って、ひるむはずです。そうしないと何かがうまくいかないから…

それを機能させようとすると、さらに多くの問題が発生します。そして、たとえそれが機能したとしても、それは簡単に理解できるものではなく、確かにエレガントではないと思います. もちろん、私が間違っていることを証明して、ここにあなたの解決策を投稿してください。

ところで、抽象ビルダーとは何かについても自問する必要があります。なぜなら、それは矛盾した言葉のように聞こえるからです。

結局のところ、この質問の範囲は広すぎると思います。答えはドメイン固有であり、要件がない場合は思いつきません。ビルダーの一般的なガイドラインは、ビルダーをできるだけシンプルにすることです。

また、関連する質問もご覧ください。

于 2012-11-06T19:28:22.880 に答える
0

あなたがジェネリックを削除するかどうか私が知る限り、

BaseThingBuilder<Thing> builder = 
            new ThingBuilder("Required!", "Base Required!")

に変更

BaseThingBuilder builder = 
            new ThingBuilder("Required!", "Base Required!")

サブクラスを最初に初期化する必要があるという制限を含め、残りはすべて同じままです。だから私はこれがジェネリックの使用を正当化するとは本当に思わない。多分私は何かが欠けています。

ビャーネ・ストロヴルプのこのようなものをずっと覚えているようです...

于 2012-11-06T21:34:48.057 に答える