Metteremo la fluent api alla sua classe "builder" separata dall'oggetto che sta creando. In questo modo, se il cliente non desidera utilizzare l'API fluente, è comunque possibile utilizzarlo manualmente e non inquina l'oggetto dominio (attenendosi al principio di responsabilità singola). In questo caso verrà creato il seguente:
-
Car
che è l'oggetto dominio
-
CarBuilder
che contiene l'API fluente
L'utilizzo sarebbe simile a questo:
var car = CarBuilder.BuildCar()
.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver)
.Build();
La classe CarBuilder
sarà simile a questa (sto usando la convenzione di denominazione C # qui):
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;
}
}
Si noti che questa classe non sarà thread-safe (ogni thread avrà bisogno della propria istanza di CarBuilder). Nota anche che, anche se l'api fluente è un concetto davvero interessante, probabilmente è eccessivo per lo scopo di creare oggetti di dominio semplici.
Questo accordo è più utile se stai creando un'API per qualcosa di molto più astratto e ha un set up e un'esecuzione più complessi, ed è per questo che funziona alla grande nelle prove unitarie e nei quadri DI. Puoi vedere alcuni altri esempi nella sezione Java di wikipedia Fluent Interface article con persistenza, gestione delle date e oggetti mock .
EDIT:
Come notato dai commenti; potresti rendere la classe Builder una classe interna statica (all'interno di Car) e Car potrebbe essere resa immutabile. Questo esempio di lasciare che l'auto sia immutabile sembra un po 'sciocco; ma in un sistema più complesso, in cui non si desidera assolutamente modificare il contenuto dell'oggetto che è stato creato, si potrebbe desiderare di farlo.
Di seguito è riportato un esempio di come eseguire sia la classe interna statica che come gestire una creazione di un oggetto immutabile che genera:
// 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();
}
}
L'utilizzo sarebbe il seguente:
var writer = ImmutableWriter
.GetBuilder()
.Write("peanut butter jelly time")
.Times(2)
.Build();
writer.Perform();
// console writes: peanut butter jelly time peanut butter jelly time
Modifica 2: Pete nei commenti ha fatto un post di blog sull'utilizzo di builder con funzioni lambda nel contesto della scrittura di unit test con oggetti di dominio complessi. È un'alternativa interessante per rendere il costruttore un po 'più espressivo.
Nel caso di CarBuilder
devi invece avere questo metodo:
public static Car Build(Action<CarBuilder> buildAction = null) {
var carBuilder = new CarBuilder();
if (buildAction != null) buildAction(carBuilder);
return carBuilder._car;
}
Che può essere usato come questo:
Car c = CarBuilder
.Build(car =>
car.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver);