編集:この質問は理論的な問題を説明していることを指摘したいと思います。必須パラメーターにコンストラクター引数を使用できること、またはAPIが正しく使用されていない場合はランタイム例外をスローできることを認識しています。ただし、コンストラクター引数やランタイムチェックを必要としないソリューションを探しています。
次のCar
ようなインターフェースがあるとします。
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
}
コメントが示唆するCar
ように、Engine
とは必須ですTransmission
が、Stereo
はオプションです。つまりbuild()
、Car
インスタンスが可能なビルダーは、ビルダーインスタンスにとの両方が既に与えられているbuild()
場合にのみメソッドEngine
をTransmission
持つ必要があります。その方法は、型チェッカーが試みが作成することは、任意のコードコンパイルを拒否しますCar
せずにインスタンスEngine
またはTransmission
。
これには、ステップビルダーが必要です。通常、次のようなものを実装します。
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
public class Builder {
public BuilderWithEngine engine(Engine engine) {
return new BuilderWithEngine(engine);
}
}
public class BuilderWithEngine {
private Engine engine;
private BuilderWithEngine(Engine engine) {
this.engine = engine;
}
public BuilderWithEngine engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
return new CompleteBuilder(engine, transmission);
}
}
public class CompleteBuilder {
private Engine engine;
private Transmission transmission;
private Stereo stereo = null;
private CompleteBuilder(Engine engine, Transmission transmission) {
this.engine = engine;
this.transmission = transmission;
}
public CompleteBuilder engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
public CompleteBuilder stereo(Stereo stereo) {
this.stereo = stereo;
return this;
}
public Car build() {
return new Car() {
@Override
public Engine getEngine() {
return engine;
}
@Override
public Transmission getTransmission() {
return transmission;
}
@Override
public Stereo getStereo() {
return stereo;
}
};
}
}
}
別のビルダークラスのチェーンがあります(Builder
、BuilderWithEngine
、CompleteBuilder
)、その追加1は、だけでなく、すべてのオプションのsetterメソッドを含む最後のクラスで、別の後にsetterメソッドが必要でした。
つまり、このステップビルダーのユーザーは、作成者が必須のセッターを使用可能にした順序に制限されます。可能な使用法の例を次に示します(すべて厳密に順序付けられていることに注意してください:engine(e)
最初に、次にtransmission(t)
、最後にオプションstereo(s)
)。
new Builder().engine(e).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).engine(e).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).engine(e).build();
new Builder().engine(e).transmission(t).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).stereo(s).build();
ただし、これはビルダーのユーザーにとって理想的ではないシナリオがたくさんあります。特にビルダーにセッターだけでなくアダーもある場合、またはビルダーの特定のプロパティが使用可能になる順序をユーザーが制御できない場合は特にそうです。
そのために私が考え得る唯一の解決策は非常に複雑です:設定された、またはまだ設定されていない必須プロパティのすべての組み合わせについて、到着する前に呼び出す必要がある可能性のある他の必須セッターを認識する専用ビルダークラスを作成しましたbuild()
メソッドが利用できる場所を示し、それらのセッターのそれぞれが、build()
メソッドを含むより一歩近いより完全なタイプのビルダーを返します。
私は以下のコードを追加しますが、私はあなたが作成することができますFSM作成するために、型システムを使用していると言うかもしれないBuilder
いずれかに変換することができ、BuilderWithEngine
またはBuilderWithTransmission
その両方が、その後に変えることができ、CompleteBuilder
、その実装build()
方法。オプションのセッターは、これらのビルダーインスタンスのいずれでも呼び出すことができます。
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
public class Builder extends OptionalBuilder {
public BuilderWithEngine engine(Engine engine) {
return new BuilderWithEngine(engine, stereo);
}
public BuilderWithTransmission transmission(Transmission transmission) {
return new BuilderWithTransmission(transmission, stereo);
}
@Override
public Builder stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class OptionalBuilder {
protected Stereo stereo = null;
private OptionalBuilder() {}
public OptionalBuilder stereo(Stereo stereo) {
this.stereo = stereo;
return this;
}
}
public class BuilderWithEngine extends OptionalBuilder {
private Engine engine;
private BuilderWithEngine(Engine engine, Stereo stereo) {
this.engine = engine;
this.stereo = stereo;
}
public CompleteBuilder transmission(Transmission transmission) {
return new CompleteBuilder(engine, transmission, stereo);
}
public BuilderWithEngine engine(Engine engine) {
this.engine = engine;
return this;
}
@Override
public BuilderWithEngine stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class BuilderWithTransmission extends OptionalBuilder {
private Transmission transmission;
private BuilderWithTransmission(Transmission transmission, Stereo stereo) {
this.transmission = transmission;
this.stereo = stereo;
}
public CompleteBuilder engine(Engine engine) {
return new CompleteBuilder(engine, transmission, stereo);
}
public BuilderWithTransmission transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
@Override
public BuilderWithTransmission stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class CompleteBuilder extends OptionalBuilder {
private Engine engine;
private Transmission transmission;
private CompleteBuilder(Engine engine, Transmission transmission, Stereo stereo) {
this.engine = engine;
this.transmission = transmission;
this.stereo = stereo;
}
public CompleteBuilder engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
@Override
public CompleteBuilder stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
public Car build() {
return new Car() {
@Override
public Engine getEngine() {
return engine;
}
@Override
public Transmission getTransmission() {
return transmission;
}
@Override
public Stereo getStereo() {
return stereo;
}
};
}
}
}
ご存知のように、必要なさまざまなビルダークラスの数はO(2 ^ n)になるため、これは適切にスケーリングされません。ここで、nは必須のセッターの数です。
したがって、私の質問:これをよりエレガントに実行できますか?
(私はJavaで動作する答えを探していますが、Scalaも受け入れられます)
.engine(e)
1つのビルダーに対して2回呼び出すとはどういう意味ですか?
build()
ことがない場合でも、電話をかけることはできません。engine(e)
transmission(t)
Engine
実装から始めて、後でより具体的な実装で上書きすることもできます。しかしengine(e)
、セッターではなくアダーである場合、これはおそらくより意味のあることですaddEngine(e)
。これはCar
、複数のエンジン/モーターを備えたハイブリッド車を生産できるビルダーに役立ちます。これは不自然な例であるため、簡潔にするために、なぜこれを行う必要があるのかについては詳しく説明しませんでした。
this
ませんか?