Je mettrais l' api couramment à sa propre classe "constructeur", séparée de l'objet qu'elle crée. De cette façon, si le client ne veut pas utiliser l’API fluide, vous pouvez toujours l’utiliser manuellement et il ne pollue pas l’objet du domaine (conformément au principe de la responsabilité unique). Dans ce cas, les éléments suivants seraient créés:
Car
qui est l'objet de domaine
CarBuilder
qui tient la API couramment
L'utilisation serait comme ceci:
var car = CarBuilder.BuildCar()
.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver)
.Build();
La CarBuilder
classe ressemblerait à ceci (j'utilise la convention de nommage C # ici):
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;
}
}
Notez que cette classe ne sera pas thread-safe (chaque thread aura besoin de sa propre instance CarBuilder). Notez également que, même si le langage api fluide est un concept vraiment génial, il est probablement excessif de créer des objets de domaine simples.
Cette offre est plus utile si vous créez une API pour quelque chose de beaucoup plus abstrait et que sa configuration et son exécution sont plus complexes. C'est pourquoi elle fonctionne parfaitement dans les tests unitaires et les infrastructures DI. Vous pouvez voir d'autres exemples dans la section Java de l'article wikipedia Fluent Interface avec les objets persistance, datation et fictif.
MODIFIER:
Comme indiqué dans les commentaires; vous pouvez faire de la classe Builder une classe interne statique (dans la voiture) et la voiture peut être rendue immuable. Cet exemple de laisser la voiture immuable semble un peu ridicule; mais dans un système plus complexe, où vous ne voulez absolument pas changer le contenu de l'objet construit, vous pouvez le faire.
Vous trouverez ci-dessous un exemple illustrant comment utiliser à la fois la classe interne statique et comment gérer une création d'objet immuable qu'elle génère:
// 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'utilisation serait la suivante:
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 dans les commentaires a publié un article sur l’utilisation des générateurs avec les fonctions lambda dans le contexte de l’écriture de tests unitaires avec des objets de domaine complexes. C'est une alternative intéressante pour rendre le constructeur un peu plus expressif.
Dans le cas de CarBuilder
vous devez avoir cette méthode à la place:
public static Car Build(Action<CarBuilder> buildAction = null) {
var carBuilder = new CarBuilder();
if (buildAction != null) buildAction(carBuilder);
return carBuilder._car;
}
Qui peut être utilisé comme ceci:
Car c = CarBuilder
.Build(car =>
car.OfBrand(Brand.Ford)
.OfModel(12345)
.PaintedIn(Color.Silver);
var car = new Car(Brand.Ford, 12345, Color.Silver);
?