Если все поля класса финальные (неизменяемые), единственный способ создать экземпляр — это вызов конструктора.
public class User {
private final String name;
private final String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
new User("John", "john@riseup.net");
Когда полей станет много, вызывать такой конструктор будет проблематично, т. к. в Java нет ни именованных параметров, ни значений параметров по умолчанию.
public class User {
private final String name;
private final String email;
private final String country;
private final String city;
private final String street;
private final String house;
public User(String name, String email, String country, String city, String street, String house) {
this.name = name;
this.email = email;
this.country = country;
this.city = city;
this.street = street;
this.house = house;
}
}
new User(/* ой-ой */);
В качестве решения проблемы большого конструктора некоторые используют билдеры:
public class UserBuilder {
private String name;
private String email;
private String country;
private String city;
private String street;
private String house;
public UserBuilder setName(String name) {
this.name = name;
return this;
}
public UserBuilder setEmail(String email) {
this.email = email;
return this;
}
public UserBuilder setCountry(String country) {
this.country = country;
return this;
}
public UserBuilder setCity(String city) {
this.city = city;
return this;
}
public UserBuilder setStreet(String street) {
this.street = street;
return this;
}
public UserBuilder setHouse(String house) {
this.house = house;
return this;
}
public User createUser() {
return new User(name, email, country, city, street, house);
}
}
new UserBuilder()
.setName("John")
.setEmail("john@riseup.net")
.setCountry("Russia")
.setCity("Moscow")
.setStreet("The Red Square")
.setHouse("somewhere")
.createUser();
У такого подхода есть единственный плюс — у сеттеров (wither'ов) есть имена.
Минусы
- язык не может принудить к вызову всех wither'ов. Если какие-то из них будут пропущены, у свойств будут дефолтные значения;
- временный объект. Используется кратковременно, потом скармливается сборщику мусора;
- каждый вызов wither'а — это вызов отдельного метода. Создание объекта посредством билдера медленнее создания через конструктор (заметно только в интерпретаторе).
Нормальное решение: уменьшить объект, чтобы снова можно было использовать конструктор.
public class User {
private final String name;
private final String email;
private final Address address;
public User(String name, String email, Address address) {
this.name = name;
this.email = email;
this.address = address;
}
}
public class Address {
private final String country;
private final String city;
private final String street;
private final String house;
public Address(String country, String city, String street, String house) {
this.country = country;
this.city = city;
this.street = street;
this.house = house;
}
}
User user = new User(
"John",
"john@riseup.net",
new Address(
"Russia", "Moscow", "The Red Square", "somewhere"
)
);
Именованные параметры
В качестве именованных параметров в билдерах используются имена сеттеров. Это помогает сделать читаемым код наподобие этого:
GeoPosition position = new GeoPosition(database, input.latitude(), input.longitude());
Address address = new AddressBuilder()
.setCountry(position.country())
.setCity(position.city())
.setStreet(position.street())
.setHouse(input.isHouseSpecified() ? input.house() : position.house())
.createAddress();
Когда используется конструктор, именованные параметры можно сэмулировать с помощью локальных переменных:
GeoPosition position = new GeoPosition(database, input.latitude(), input.longitude());
final String country = position.country();
final String city = position.city();
final String street = position.street();
final String house = input.isHouseSpecified() ? input.house() : position.house();
Address address = new Address(country, city, street, house);
Здесь задача правильно назвать переменные полностью ложится на плечи того, кто создаёт объект. Зато нет необходимости поддерживать два класса с одинаковыми данными и создавать промежуточный объект.