かなり複雑な不変データ型があり、ビルダーオブジェクトを使用してインスタンス化しています。現在、私はファイルを解析し、ビルダーでさまざまなフィールドを設定してからオブジェクトを構築する設定をしています。ただし、使用しているファイルはエラーが発生しやすいため、最初にビルドされていないオブジェクトの検証を行います。そして、オブジェクトが検証に合格しなかった場合に例外をスローしたくないことに注意することが重要です。これは、それがinvalidであるという意味ではなく、単に正しくないためです。
しかし、私がビルダーに会うのに慣れている方法は、ビルダーが「書き込み専用」であるということです。たとえば、グアバのImmutableList.Builder
クラスでは、送信したデータを表示することができないため、フリーズする前にデータを編集する必要がある場合は、ビルダーとしてArrayList
sを使用します。
または、先に進んでエラーのあるオブジェクトをビルドし、ビルドされたオブジェクトのデータがプリロードされた新しいビルダーを作成し、データを検査して編集し、再ビルドすることもできます。ただし、これは無駄でエレガントではありません。
あなたのビルダーオブジェクトにゲッターメソッドを置くことはコード臭いと考えられていますか?直感的には少し奇妙に思えますが、このタイプの問題に何度も直面しました。これにより、ビルダーに渡すデータの一部のアクセス可能なコピーを保持して、後で見てください。ゲッターを持つだけで物事が簡単になります。
ビルダークラスにゲッターを渡して、渡されたデータを検査できるようにすること自体は何も問題はないと思います。RobertHarveyが述べたように、ゲッターはパターンの一部ではありませんが、実用的な解決策は、いくつかの教義に固執するよりも優れています。そして、パターンを実装するための「唯一かつ正しい方法」はありません。設計パターンは常に、それらを実装する方法にある程度の自由度を与えます。
ただし、その1つのクラスにさまざまな責任を割り当てないように注意する必要があります(ビルダークラスにゲッターがある場合、これはこれを示している可能性があります)。ビルダーが入力データのリポジトリー、データのバリデーター、ビルダー自体になる場合は、責任が多すぎるため、異なるクラスに分割することを検討する必要があります。
私は確かにビルダーのゲッターで眉をひそめるでしょう、そしてそれは単一責任パターンの違反のように見えます。
ビルダーの前に別の検証コンポーネントが必要なようです。したがって、処理のチェーンはファイルを読み取り、パラメーターを検証し、ビルダーにフィードします。別の方法(これは一般的には良い方法です)は、構築時に事後条件を実行するように最終オブジェクトをコーディングし、不正なパラメーターが渡された場合に例外をスローすることです。そのようなオブジェクトを正しく構築できないようにしたい場合、ビルダーを使用する場合、またはビルダーを使用しない場合は、これは一般的に良い習慣です。
ビルダーの重要な使用例の1つは、テストプロセスをプロビジョニングするときに発生します。多くの場合、重要なデータ構造を構築する必要があります。その多くは、テスト対象のコードに渡されたときに不変であり、ビルダーは使用する自然なパターンです。
テストのセットアップは、ビルダーの作成といくつかの開始プロパティの設定で構成されます。個々のテストは、最終的なデータ構造が構築されるまで、さまざまな方向にビルダーを変更します。ビルダーのコンテンツは累積的であるため、プロセスの一部のステップでは、初期の段階で設定された値を知る必要がある場合があります。
短い答え:データ構造の構築に複数のフェーズが必要な場合、ゲッターは後続のフェーズでローカルの知識を提供する自然な方法です。
オブジェクトでファクトリメソッドを実行して、パラメータをnullにすることはできません。それはvaladitionがあるべき場所です。それらは例外を投げることができます。
ビルダーについては、抽象ファクトリパターンに置き換えて、例外をスローさせます。読み込みについては、工場のリストを用意してください。オブジェクトを作成してみて、オブジェクトがnullでなく、例外がスローされない場合は、オブジェクトを返します。
それが遅すぎる場合は、独自のスレッドでロードしてリストに追加してください。次に、ローダーにリストを渡してクラスを実行します。
考慮してください:
public abstract class Entity {
public static abstract class Builder<T extends Entity, B extends Builder<T, B>> {
private final T object;
private final B thisBuilder;
public Builder() {
this.object = createObject();
this.thisBuilder = thisBuilder();
}
protected abstract T createObject();
protected abstract B thisBuilder();
public T build() {
return getObject();
}
protected T getObject() {
return this.object;
}
}
}
上記のビジネスオブジェクトのスーパークラスには、ビジネスオブジェクト内のデータを使用して初期化する方法を提供するBuilderクラスが含まれていますが、必ずしもpublic
アクセサーを介してデータを公開するわけではありません。
Builderクラスを使用するクラスは次のとおりです。
public final class Address extends Entity {
private String city;
protected synchronized String getCity() {
return this.city == null ? "" : this.city;
}
protected void setCity(String city) {
this.city = city;
}
public static final class Builder extends Entity.Builder<Address, Builder> {
@Override
protected Builder thisBuilder() {
return this;
}
@Override
protected Address createObject() {
return new Address();
}
public Builder withCity(String city) {
getObject().setCity(city);
return thisBuilder();
}
}
}
これで、次のようにAddress
インスタンスを構築できます。
Address address = new Address.Builder()
.withCity( "Vancouver" )
.build();
Builderクラス内でアクセサーが公開されていないため、パッケージの外部でデータが公開されていません。また、ビルダークラスと構築中のオブジェクトの間でデータが複製されていません(他のデザインが示すように)。
このアプローチの欠点は、機能のわずかな重複です。つまり、ビルダーのwithXYZ
メソッドは、ビルドされるクラスのsetXYZ
アクセサーを介してデータ転送を委任します。
メソッドの呼び出し順序を気にする必要のない継承が可能です。例えば:
public abstract class AbstractDate extends Entity {
private Date date;
protected synchronized Date getDate() {
return this.date == null ? new Date() : this.date;
}
protected void setDate(Date date) {
this.date = date;
}
protected static abstract class Builder
<T extends AbstractDate, B extends Builder<T, B>> extends Entity.Builder<T, B> {
public B withDate(int year, int month, int day) {
getObject().setDate(createDate(year, month, day));
return thisBuilder();
}
}
}
また、メソッド名にwith
接頭辞を使用すると、オートコンプリートに役立ちます。
Builder内にアクセサメソッドがあるのは奇妙ですか?むしろ、おそらく、それらは必要ではありません。