ฉันจะใส่API อย่างคล่องแคล่วให้กับมันเป็น "ผู้สร้าง" แยกชั้นจากวัตถุที่มันกำลังสร้าง ด้วยวิธีนี้หากลูกค้าไม่ต้องการใช้ API ได้อย่างคล่องแคล่วคุณยังสามารถใช้งานได้ด้วยตนเองและไม่ทำให้เกิดความเสียหายกับวัตถุโดเมน (ปฏิบัติตามหลักการความรับผิดชอบเดียว) ในกรณีนี้จะมีการสร้างสิ่งต่อไปนี้:
Car
ซึ่งเป็นวัตถุโดเมน
CarBuilder
ซึ่งเก็บ API ได้อย่างคล่องแคล่ว
การใช้งานจะเป็นเช่นนี้:
var car = CarBuilder.BuildCar()
.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver)
.Build();
CarBuilder
ชั้นจะมีลักษณะเช่นนี้ (ฉันใช้ C # การตั้งชื่อการประชุมที่นี่):
public class CarBuilder {
private Car _car;
/// Constructor
public CarBuilder() {
_car = new Car();
SetDefaults();
}
private void SetDefaults() {
this.OfBrand(Brand.Ford);
// you can continue the chaining for
// other default values
}
/// Starts an instance of the car builder to
/// build a new car with default values.
public static CarBuilder BuildCar() {
return new CarBuilder();
}
/// Sets the brand
public CarBuilder OfBrand(Brand brand) {
_car.SetBrand(brand);
return this;
}
// continue with OfModel(...), PaintedIn(...), and so on...
// that returns "this" to allow method chaining
/// Returns the built car
public Car Build() {
return _car;
}
}
โปรดทราบว่าคลาสนี้จะไม่ปลอดภัยสำหรับเธรด (แต่ละเธรดจำเป็นต้องใช้อินสแตนซ์ CarBuilder ของตัวเอง) นอกจากนี้โปรดทราบว่าแม้ว่า api ที่คล่องแคล่วเป็นแนวคิดที่เจ๋งจริงๆมันอาจเกินความจริงในการสร้างวัตถุโดเมนแบบง่าย
ข้อตกลงนี้มีประโยชน์มากขึ้นถ้าคุณกำลังสร้าง API สำหรับสิ่งที่เป็นนามธรรมมากกว่าและมีการตั้งค่าและการใช้งานที่ซับซ้อนมากขึ้นซึ่งเป็นเหตุผลว่าทำไมจึงใช้งานได้ดีในการทดสอบหน่วยและเฟรมเวิร์ก DI คุณสามารถดูตัวอย่างอื่น ๆ ได้ในส่วน Java ของบทความ wikipedia Fluent Interface ที่มีการคงอยู่การจัดการวันที่และการจำลองวัตถุ
แก้ไข:
ตามที่ระบุไว้จากความคิดเห็น; คุณสามารถทำให้คลาส Builder เป็นคลาสภายในแบบคงที่ (ภายในรถยนต์) และรถยนต์อาจไม่เปลี่ยนรูป ตัวอย่างของการปล่อยให้รถไม่เปลี่ยนรูปนั้นดูโง่ไปหน่อย แต่ในระบบที่ซับซ้อนมากขึ้นซึ่งคุณไม่ต้องการเปลี่ยนเนื้อหาของวัตถุที่ถูกสร้างขึ้นอย่างแน่นอนคุณอาจต้องการทำมัน
ด้านล่างเป็นตัวอย่างหนึ่งของวิธีการทำทั้งคลาสภายในแบบสแตติกและวิธีจัดการกับการสร้างวัตถุที่ไม่เปลี่ยนรูปที่มันสร้างขึ้นมา:
// the class that represents the immutable object
public class ImmutableWriter {
// immutable variables
private int _times; private string _write;
// the "complex" constructor
public ImmutableWriter(int times, string write) {
_times = times;
_write = write;
}
public void Perform() {
for (int i = 0; i < _times; i++) Console.Write(_write + " ");
}
// static inner builder of the immutable object
protected static class ImmutableWriterBuilder {
// the variables needed to construct the immutable object
private int _ii = 0; private string _is = String.Empty;
public void Times(int i) { _ii = i; }
public void Write(string s) { _is = s; }
// The stuff is all built here
public ImmutableWriter Build() {
return new ImmutableWriter(_ii, _is);
}
}
// factory method to get the builder
public static ImmutableWriterBuilder GetBuilder() {
return new ImmutableWriterBuilder();
}
}
การใช้งานจะเป็นดังต่อไปนี้:
var writer = ImmutableWriter
.GetBuilder()
.Write("peanut butter jelly time")
.Times(2)
.Build();
writer.Perform();
// console writes: peanut butter jelly time peanut butter jelly time
แก้ไข 2: Peteในความคิดเห็นที่โพสต์บล็อกเกี่ยวกับการใช้ผู้สร้างกับฟังก์ชั่นแลมบ์ดาในบริบทของการเขียนการทดสอบหน่วยด้วยวัตถุโดเมนที่ซับซ้อน มันเป็นทางเลือกที่น่าสนใจที่จะทำให้ผู้สร้างแสดงออกได้มากขึ้น
ในกรณีที่CarBuilder
คุณจำเป็นต้องมีวิธีนี้แทน:
public static Car Build(Action<CarBuilder> buildAction = null) {
var carBuilder = new CarBuilder();
if (buildAction != null) buildAction(carBuilder);
return carBuilder._car;
}
ซึ่งสามารถใช้เป็นสิ่งนี้:
Car c = CarBuilder
.Build(car =>
car.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver);
var car = new Car(Brand.Ford, 12345, Color.Silver);
?