У меня проблема с шаблоном проектирования. Скажем, у меня есть простой класс:
public class Person {
private String name;
private String surname;
private int age;
private Person(){}
public String toString()
{
return "name: "+name+" surname: "+surname+" age: "+age;
}
public static final class Builder{
private String name;
private String surname;
private int age;
public Builder name (String name){
this.name = name;
return this;
}
public Builder surname (String surname){
this.surname = surname;
return this;
}
public Builder age (int age){
this.age = age;
return this;
}
public Person build(){
Person person = new Person();
person.name=name;
person.surname=surname;
person.age=age;
return person;
}
}
}
Он работает нормально, но не требуется устанавливать переменные перед вызовом метода "build". Как я мог это изменить?
@BoristheSpider, существует так много разных версий паттерна Builder. Не могли бы вы дать ссылку на тот, о котором вы говорите?
Простым способом добиться этого было бы создание исключения в build()
, если не все было установлено. Наряду с документацией этого может быть уже достаточно, но при этом не будет никаких проверок во время компиляции.
Альтернативой, если вы согласны с соблюдением некоторого порядка, может быть использование объектов "step":
class Builder {
private String name;
private String surname;
private int age;
//start with the name and create a new builder
public static SurnameStep name(String name) {
Builder builder = new Builder();
builder.name = name;
return builder.new SurnameStep();
}
public class SurnameStep {
public AgeStep surname(String surname) {
Builder.this.surname = surname;
return new AgeStep();
}
}
public class AgeStep {
public Builder age(int age) {
Builder.this.age = age;
return Builder.this;
}
}
public Person build() {
//build person here
}
}
Тогда использование должно быть таким:
Person p = Builder.name("John").surname("Smith").age(42).build();
Имя, фамилия и возраст должны быть указаны в указанном порядке, прежде чем build()
станет доступен.
Если все поля являются обязательными, вы также можете избавиться от build()
и создать Person
уже в age(int)
.
Также обратите внимание, что вместо того, чтобы помещать шаги в Builder
, вы можете поместить их прямо в Person
. Статический метод "init" name(String)
уже создал бы экземпляр Person
, но он станет доступным только после вызова age(int)
:
//expanded to make it more obvious: the steps don't provide access to the Person until all have been executed
SurnameStep surnameStep = Person.name("John");
AgeStep ageStep = surnameStep .surname("Smith");
Person johnSmith = ageStep.age(42);
Вы можете улучшить этот код, выполнив следующие действия:
public class Person {
public Person(String first, String last, int age) {
this.first = first;
this.last = last;
this.age = age;
}
}
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
if (first == null && last == null) {
throw new IllegalArgumentException("first or last name is null");
}
public static Builder someAdultPerson() {
return new Builder("Erika", "Mustermann", 18);
}
interface Builder<T> {
T build();
static <C> given(Builder<C> builder) { return builder.build(); }
}
...
class Person {
class Builder implements my.example.Builder<Person> { ... }
}
...
import static Person.Builder.someAdultPerson;
...
Person person = given(someAdultPerson().age(27));
Вы можете использовать построитель на основе лямбда для принудительного выполнения последовательности вызовов методов, прежде чем пользователь сможет вызвать
build()
.