Sunting: Saya ingin menunjukkan bahwa pertanyaan ini menjelaskan masalah teoritis, dan saya sadar bahwa saya dapat menggunakan argumen konstruktor untuk parameter wajib, atau melempar pengecualian runtime jika API digunakan secara tidak benar. Namun, saya mencari solusi yang tidak memerlukan argumen konstruktor atau pengecekan runtime.
Bayangkan Anda memiliki Car
antarmuka seperti ini:
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
}
Seperti yang disarankan oleh komentar, a Car
harus memiliki Engine
dan Transmission
tetapi a Stereo
adalah opsional. Bahwa sarana Builder yang dapat build()
menjadi Car
contoh hanya pernah memiliki build()
metode jika Engine
dan Transmission
memiliki keduanya telah diberikan ke contoh pembangun. Dengan cara itu pemeriksa tipe akan menolak untuk mengkompilasi kode apa pun yang mencoba membuat Car
instance tanpa Engine
atau Transmission
.
Ini panggilan untuk Pembangun Langkah . Biasanya Anda akan menerapkan sesuatu seperti ini:
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;
}
};
}
}
}
Ada rantai kelas pembangun yang berbeda ( Builder
, BuilderWithEngine
, CompleteBuilder
), bahwa salah satu add diperlukan metode setter demi satu, dengan kelas terakhir yang berisi semua metode setter opsional juga.
Ini berarti bahwa pengguna pembuat langkah ini terbatas pada urutan di mana penulis membuat setter wajib tersedia . Berikut ini adalah contoh kemungkinan penggunaan (perhatikan bahwa semuanya dipesan dengan ketat: engine(e)
pertama, diikuti oleh transmission(t)
, dan akhirnya opsional 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();
Namun, ada banyak skenario di mana ini tidak ideal untuk pengguna pembangun, terutama jika pembangun tidak hanya seter, tetapi juga adders, atau jika pengguna tidak dapat mengontrol urutan di mana properti tertentu untuk pembangun akan tersedia.
Satu-satunya solusi yang dapat saya pikirkan untuk itu sangat berbelit-belit: Untuk setiap kombinasi properti wajib yang telah ditetapkan atau yang belum ditetapkan, saya membuat kelas pembangun khusus yang tahu potensi mana yang harus ditetapkan oleh pembuat seting wajib lainnya sebelum tiba di sebuah nyatakan di mana build()
metode harus tersedia, dan masing-masing setter mengembalikan tipe pembangun yang lebih lengkap yang selangkah lebih dekat untuk memuat build()
metode.
Saya menambahkan kode di bawah ini, tetapi Anda mungkin mengatakan bahwa saya menggunakan sistem tipe untuk membuat FSM yang memungkinkan Anda membuat Builder
, yang dapat diubah menjadi a BuilderWithEngine
atau BuilderWithTransmission
, yang keduanya kemudian dapat diubah menjadi CompleteBuilder
, yang mengimplementasikanbuild()
metode. Setter opsional dapat digunakan pada instance builder ini.
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;
}
};
}
}
}
Seperti yang Anda tahu, ini tidak baik skala, karena jumlah kelas pembangun yang berbeda diperlukan akan O (2 ^ n) di mana n adalah jumlah setter wajib.
Maka pertanyaan saya: Apakah ini bisa dilakukan dengan lebih elegan?
(Saya mencari jawaban yang berfungsi dengan Java, meskipun Scala juga dapat diterima)
.engine(e)
dua kali untuk satu pembangun?
build()
jika Anda belum menelepon engine(e)
dan transmission(t)
sebelumnya.
Engine
implementasi default , dan kemudian menimpanya dengan implementasi yang lebih spesifik. Tapi kemungkinan besar ini akan lebih masuk akal jika engine(e)
tidak setter, tetapi penambah: addEngine(e)
. Ini akan berguna untuk Car
pembangun yang dapat menghasilkan mobil hybrid dengan lebih dari satu mesin / motor. Karena ini adalah contoh yang dibuat-buat, saya tidak masuk ke detail tentang mengapa Anda mungkin ingin melakukan ini - untuk singkatnya.
this
?