แก้ไข:ฉันต้องการจะชี้ให้เห็นว่าคำถามนี้อธิบายถึงปัญหาทางทฤษฎีและฉันรู้ว่าฉันสามารถใช้อาร์กิวเมนต์ตัวสร้างสำหรับพารามิเตอร์บังคับหรือส่งข้อยกเว้น runtime ถ้าใช้ 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
) ที่เพิ่มวิธีการตั้งค่าที่จำเป็นหนึ่งหลังจากที่อื่นกับการเรียนที่ผ่านมามีทั้งหมดวิธีการตั้งค่าตัวเลือกเช่นกัน
ซึ่งหมายความว่าผู้ใช้งานของผู้สร้างขั้นตอนนี้จะถูก จำกัด อยู่ในลำดับที่ผู้เขียนทำให้ผู้เซ็ตอัพสามารถใช้งานได้ นี่คือตัวอย่างของการใช้งานที่เป็นไปได้ (โปรดทราบว่าทั้งหมดนั้นมีคำสั่งอย่างเคร่งครัด: 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 ที่ให้คุณสร้าง a Builder
ซึ่งสามารถเปลี่ยนเป็น a BuilderWithEngine
หรือBuilderWithTransmission
ซึ่งทั้งสองสามารถกลายเป็น a CompleteBuilder
ซึ่งใช้build()
วิธี. setters ที่เป็นตัวเลือกสามารถถูกเรียกใช้บนอินสแตนซ์ตัวสร้างใด ๆ เหล่านี้
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)
สองครั้งสำหรับผู้สร้างหนึ่งหมายความว่าอย่างไร
build()
ถ้าคุณยังไม่ได้เรียกengine(e)
และtransmission(t)
ก่อนที่จะ
Engine
ใช้งานเริ่มต้นและเขียนทับมันในภายหลังด้วยการใช้งานที่เฉพาะเจาะจงมากขึ้น แต่ส่วนใหญ่นี้จะทำให้ความรู้สึกมากขึ้นถ้าengine(e)
ไม่ได้เป็นหมา addEngine(e)
แต่เป็นงูพิษที่: สิ่งนี้จะเป็นประโยชน์สำหรับCar
ผู้สร้างที่สามารถผลิตรถยนต์ไฮบริดที่มีมากกว่าหนึ่งเครื่องยนต์ / มอเตอร์ เนื่องจากนี่เป็นตัวอย่างที่วางแผนไว้ฉันไม่ได้ลงรายละเอียดว่าทำไมคุณถึงอยากทำเช่นนี้ - เพื่อความกระชับ
this
?