Я бы поместил свободный API в его собственный класс "builder" отдельно от объекта, который он создает. Таким образом, если клиент не хочет использовать свободный 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 статьи Википедии о свободном интерфейсе с объектами персистентности, обработки дат и фиктивных объектов.
РЕДАКТИРОВАТЬ:
Как отмечено в комментариях; вы можете сделать класс Builder статическим внутренним классом (внутри Car), и Car можно сделать неизменным. Этот пример того, как автомобиль был неизменным, кажется немного глупым; но в более сложной системе, где вы абсолютно не хотите изменять содержимое построенного объекта, вы можете захотеть это сделать.
Ниже приведен пример того, как сделать статический внутренний класс и обработать создание неизменного объекта, который он создает:
// 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: Пит в комментариях сделал сообщение в блоге об использовании компоновщиков с лямбда-функциями в контексте написания модульных тестов со сложными объектами домена. Это интересная альтернатива, чтобы сделать строителя немного более выразительным.
В случае, если 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);
?