Chỉnh sửa: Tôi muốn chỉ ra rằng câu hỏi này mô tả một vấn đề lý thuyết và tôi biết rằng tôi có thể sử dụng các đối số hàm tạo cho các tham số bắt buộc hoặc ném ngoại lệ thời gian chạy nếu API được sử dụng không chính xác. Tuy nhiên, tôi đang tìm kiếm một giải pháp không yêu cầu đối số hàm tạo hoặc kiểm tra thời gian chạy.
Hãy tưởng tượng bạn có một Car
giao diện như thế này:
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
}
Như các ý kiến sẽ đề xuất, Car
phải có một Engine
và Transmission
nhưng là một Stereo
tùy chọn. Điều đó có nghĩa một Builder có thể build()
một Car
trường hợp duy nhất bao giờ nên có một build()
phương pháp nếu một Engine
và Transmission
có cả hai đã được trao cho các trường hợp xây dựng. Bằng cách đó, trình kiểm tra loại sẽ từ chối biên dịch bất kỳ mã nào cố gắng tạo một Car
cá thể mà không có Engine
hoặc Transmission
.
Điều này gọi cho một Builder Builder . Thông thường, bạn sẽ thực hiện một cái gì đó như thế này:
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;
}
};
}
}
}
Có một chuỗi của các tầng lớp người xây dựng khác nhau ( Builder
, BuilderWithEngine
, CompleteBuilder
), mà add một đòi hỏi phương pháp setter sau khác, với lớp cuối cùng có chứa tất cả các phương pháp setter tùy chọn là tốt.
Điều này có nghĩa là người dùng của trình xây dựng bước này bị giới hạn theo thứ tự mà tác giả đã tạo ra các setters bắt buộc có sẵn . Dưới đây là một ví dụ về các cách sử dụng có thể (lưu ý rằng tất cả chúng đều được sắp xếp nghiêm ngặt: engine(e)
đầu tiên, tiếp theo transmission(t)
và cuối cùng là tùy chọn 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();
Tuy nhiên, có rất nhiều tình huống trong đó điều này không lý tưởng cho người dùng của nhà xây dựng, đặc biệt là nếu nhà xây dựng không chỉ setters, mà còn các trình bổ sung hoặc nếu người dùng không thể kiểm soát thứ tự mà các thuộc tính nhất định cho nhà xây dựng sẽ khả dụng.
Giải pháp duy nhất tôi có thể nghĩ ra là rất phức tạp: Đối với mọi sự kết hợp của các thuộc tính bắt buộc đã được đặt hoặc chưa được đặt, tôi đã tạo một lớp trình tạo chuyên dụng để biết các setters bắt buộc tiềm năng nào khác cần được gọi trước khi đến trạng thái nơi build()
phương thức nên có sẵn và mỗi một trong số các setters đó trả về một kiểu trình xây dựng hoàn chỉnh hơn, một bước gần hơn để chứa một build()
phương thức.
Tôi đã thêm mã bên dưới, nhưng bạn có thể nói rằng tôi đang sử dụng hệ thống loại để tạo một FSM cho phép bạn tạo một Builder
, có thể biến thành một BuilderWithEngine
hoặc BuilderWithTransmission
, sau đó cả hai có thể được chuyển thành một CompleteBuilder
, trong đó thực hiệnbuild()
phương pháp. Setters tùy chọn có thể được gọi trên bất kỳ trường hợp xây dựng nào.
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;
}
};
}
}
}
Như bạn có thể nói, điều này không có quy mô tốt, vì số lượng các lớp xây dựng khác nhau được yêu cầu sẽ là O (2 ^ n) trong đó n là số lượng setters bắt buộc.
Do đó câu hỏi của tôi: Điều này có thể được thực hiện thanh lịch hơn?
(Tôi đang tìm kiếm một câu trả lời hoạt động với Java, mặc dù Scala cũng sẽ được chấp nhận)
.engine(e)
hai lần cho một người xây dựng có nghĩa là gì?
build()
nếu bạn chưa gọi engine(e)
và transmission(t)
trước đó.
Engine
triển khai mặc định và sau đó ghi đè lên nó bằng một triển khai cụ thể hơn. Nhưng rất có thể điều này sẽ có ý nghĩa hơn nếu engine(e)
không phải là một setter, mà là một adder : addEngine(e)
. Điều này sẽ hữu ích cho một Car
nhà chế tạo có thể sản xuất xe hơi lai với nhiều động cơ / động cơ. Vì đây là một ví dụ giả định, tôi đã không đi sâu vào chi tiết về lý do tại sao bạn có thể muốn làm điều này - cho ngắn gọn.
this
?