Про паттерн Builder сказано достаточно. Его достоинства очевидны. Есть изящные варианты замены (Элегантный Builder на Java) уменьшающие количество кода, и генераторы, вообще сводящие boilerplate на нет. Но иногда можно сделать еще проще, причем не прибегая к сторонним библиотекам.
Я использую подход с типизацией параметров:
Как видно, классы типа Point(int x, int y) имеют всего пару полей, но от ошибок инициализации там тоже сложно удержаться. Искусственные внутренние типы Width и Height вносят однозначность в семантику инициализации.
Применение не сказать, чтобы было очень утомительное
Для примера, вот как бы это выглядело с применением Builder
Очевидно, что на малом количестве полей, да еще и объектного типа подход с типизацией параметров выигрывает у Builder по объему кода процентов на 30.
Я использую подход с типизацией параметров:
// Удобный суперкласс для типизации объектных параметров
public class Value<V> {
final V value;
Value(V value) {
this.value = value;
}
}
//--------------------------------------------------------------------------------
public class PointFeatured{
// Примитивные типы оборачиваются в классы напрямую
public static final class Width {
final int value;
public Width(int value) {
this.value = value;
}
}
public static final class Height{
final int value;
public Height(int value) {
this.value = value;
}
}
// Объектные типы оборачиваются в наследников generic-класса Value<V>
public static final class LabelA extends Value<String>{
public LabelA(String value) {
super(value);
}
}
public static final class LabelB extends Value<String>{
public LabelB(String value) {
super(value);
}
}
public final int width;
public final int height;
public final String labelA;
public final String labelB;
public PointFeatured(@NotNull Width width, @NotNull Height height,
@NotNull LabelA labelA, @NotNull LabelB labelB) {
this.width = width.value;
this.height = height.value;
this.labelA= labelA.value;
this.labelB = labelB.value;
}
}
Как видно, классы типа Point(int x, int y) имеют всего пару полей, но от ошибок инициализации там тоже сложно удержаться. Искусственные внутренние типы Width и Height вносят однозначность в семантику инициализации.
Применение не сказать, чтобы было очень утомительное
@Test
public void withTypes() {
PointFeatured point = new PointFeatured(
new Width(10),
new Height(20),
new LabelA("A"),
new LabelB("B"));
Assert.assertEquals(10, point.width);
Assert.assertEquals(20, point.height);
Assert.assertEquals("A", point.labelA);
Assert.assertEquals("B", point.labelB);
}
Для примера, вот как бы это выглядело с применением Builder
public class PointWithBuilder{
public static Builder builder() {
return new Builder();
}
public static class Builder {
private int width;
private int height;
private String labelA;
private String labelB;
Builder() {
}
public Builder width(int width) {
this.width = width;
return this;
}
public Builder height(int height) {
this.height = height;
return this;
}
public Builder labelA(String labelA) {
this.labelA = labelA;
return this;
}
public Builder labelB(String labelB) {
this.labelB = labelB;
return this;
}
public PointWithBuilder build() {
return new PointWithBuilder(width, height, labelA, labelB);
}
}
public final int width;
public final int height;
public final String labelA;
public final String labelB;
private PointWithBuilder(int width, int height, String labelA, String labelB) {
this.width = width;
this.height = height;
this.labelA= labelA;
this.labelB = labelB;
}
}
//---
@Test
public void withBuilder() {
PointWithBuilder point = PointWithBuilder
.builder()
.width(10)
.height(20)
.labelA("A")
.labelB("B")
.build();
Assert.assertEquals(10, point.width);
Assert.assertEquals(20, point.height);
Assert.assertEquals("A", point.labelA);
Assert.assertEquals("B", point.labelB);
}
Очевидно, что на малом количестве полей, да еще и объектного типа подход с типизацией параметров выигрывает у Builder по объему кода процентов на 30.