Ich würde die fließende API einer eigenen "Builder" -Klasse zuweisen, die von dem Objekt, das sie erstellt, getrennt ist. Wenn der Client die flüssige API nicht verwenden möchte, kann sie dennoch manuell verwendet werden, und das Domänenobjekt wird nicht verschmutzt (unter Einhaltung des Grundsatzes der einmaligen Verantwortung). In diesem Fall würde Folgendes erstellt:
Car
Welches ist das Domain-Objekt
CarBuilder
welches die flüssige API enthält
Die Verwendung wäre wie folgt:
var car = CarBuilder.BuildCar()
.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver)
.Build();
Die CarBuilder
Klasse würde folgendermaßen aussehen (ich verwende hier die C # -Namenskonvention):
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;
}
}
Beachten Sie, dass diese Klasse nicht threadsicher ist (jeder Thread benötigt eine eigene CarBuilder-Instanz). Beachten Sie auch, dass eine flüssige API zwar ein wirklich cooles Konzept ist, aber wahrscheinlich zu viel des Guten ist, um einfache Domänenobjekte zu erstellen.
Dieser Deal ist nützlicher, wenn Sie eine API für etwas viel Abstrakteres erstellen und eine komplexere Einrichtung und Ausführung haben. Deshalb eignet er sich hervorragend für Unit-Tests und DI-Frameworks. Weitere Beispiele finden Sie im Java-Abschnitt des Wikipedia Fluent Interface-Artikels mit Informationen zu Persistenz, Datumsangaben und Scheinobjekten.
BEARBEITEN:
Wie aus den Kommentaren hervorgeht; Sie könnten die Builder-Klasse zu einer statischen inneren Klasse machen (innerhalb von Car) und Car könnte unveränderlich gemacht werden. Dieses Beispiel, Auto unveränderlich zu lassen, scheint ein bisschen albern zu sein; Aber in einem komplexeren System, in dem Sie den Inhalt des erstellten Objekts absolut nicht ändern möchten, möchten Sie dies möglicherweise tun.
Im Folgenden finden Sie ein Beispiel für die Ausführung der statischen inneren Klasse und für den Umgang mit der Erstellung eines unveränderlichen Objekts, das erstellt wird:
// 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();
}
}
Die Verwendung wäre die folgende:
var writer = ImmutableWriter
.GetBuilder()
.Write("peanut butter jelly time")
.Times(2)
.Build();
writer.Perform();
// console writes: peanut butter jelly time peanut butter jelly time
Edit 2: Pete hat in den Kommentaren einen Blogbeitrag über die Verwendung von Buildern mit Lambda-Funktionen im Kontext des Schreibens von Komponententests mit komplexen Domänenobjekten verfasst. Es ist eine interessante Alternative, um den Builder etwas ausdrucksvoller zu gestalten.
Wenn CarBuilder
Sie stattdessen diese Methode benötigen:
public static Car Build(Action<CarBuilder> buildAction = null) {
var carBuilder = new CarBuilder();
if (buildAction != null) buildAction(carBuilder);
return carBuilder._car;
}
Welches kann wie folgt verwendet werden:
Car c = CarBuilder
.Build(car =>
car.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver);
var car = new Car(Brand.Ford, 12345, Color.Silver);
?