Можно ли в Java с помощью аннотаций убрать дублирования полей и сеттеров в статическом классе и его родительском классе?
В погоне за лаконичным, но при этом обязующим не забыть сделать необходимые действия синтаксисом реализовал билдер (паттерн для передачи большого количества параметров), позволяющий создавать объекты таким образом:
new Person(
Person.specifyParameters(new Person.RequiredParameters() {
@Override public void setID() { this.ID = "1"; }
@Override public void setFamilyName() { this.familyName = "毛"; }
})
// и дальше опциональные параметры...
.setGivenName("澤東")
.setFamilyNameSpell("Мао")
.setGivenNameSpell("Цзэдун")
);
Его преимущества:
- Последовательность параметров не имеет значения.
- Пользователь не может забыть, КАКИЕ параметры обязательны к инициализации, а какие - опциональные. Хотя, например, в
setID()пользователь и может по невнимательности просеттить что-то вместо ID, он не может забыть про то, что надо реализовать методsetID- иначе программа не скомпилируется.
Ну и ещё, не знаю, считать это достоинством или нет, класс в классе Person инкапсулирован билдер для своих же инстансов. Объективно пока не могу доказать, что это плохо, но мне как-то не хочется выносить билдер в отдельный класс.
Недостаток реализации - дублирование полей и сеттеров в классе Person и в его дочернем классе ParametersObject. Была бы возможность сослаться на собственный статический абстрактный класс, работы было бы меньше, а пока реализация такова:
package dev.example.BusinessRules.Entities;
public class Person {
private String ID;
private String familyName;
private String familyNameSpell;
private String givenName;
private String givenNameSpell;
public Person(ParametersObject parametersObject) {
parametersObject.initializeSpecifiedFields(this);
}
public static ParametersObject specifyParameters(Person.RequiredParameters requiredParameters) {
return new Person.ParametersObject(requiredParameters);
}
public String getID() { return this.ID; }
public String getFamilyName() {
return familyName;
}
public Person setFamilyName(String familyName) {
this.familyName = familyName;
return this;
}
public String getGivenName() { return givenName; }
public Person setGivenName(String givenName) {
this.givenName = givenName;
return this;
}
public String getFamilyNameSpell() { return familyNameSpell; }
public Person setFamilyNameSpell(String familyNameSpell) {
this.familyNameSpell = familyNameSpell;
return this;
}
public String getGivenNameSpell() {
return givenNameSpell;
}
public Person setGivenNameSpell(String givenNameSpell) {
this.givenNameSpell = givenNameSpell;
return this;
}
public static class ParametersObject {
private final String ID;
private final String familyName;
private String givenName;
private String familyNameSpell;
private String givenNameSpell;
public ParametersObject(Person.RequiredParameters requiredParameters) {
this.ID = requiredParameters.ID;
this.familyName = requiredParameters.familyName;
}
public void initializeSpecifiedFields(Person person) {
person.ID = this.ID;
person.familyName = this.familyName;
person.givenName = this.givenName;
person.familyNameSpell = this.familyNameSpell;
person.givenNameSpell = this.givenNameSpell;
}
public ParametersObject setGivenName(String givenName) { this.givenName = givenName; return this; }
public ParametersObject setFamilyNameSpell(String familyNameSpell) { this.familyNameSpell = familyNameSpell; return this; }
public ParametersObject setGivenNameSpell(String givenNameSpell) { this.givenNameSpell = givenNameSpell; return this; }
}
public static abstract class RequiredParameters {
public String ID;
public String familyName;
public abstract void setID();
public abstract void setFamilyName();
}
}
Прочитав уроки по Java-аннотациям, в большинстве случаев так и не понял, что они конкретно делают и вообще не понял, КАК они это делают. Но вопрос такой: можно ли внутри ParametersObject уменьшить количество кода с помощью аннотаций? Убрать нужно в первую очередь дублирования полей и геттеров.
Цель всей этой акробатики - приблизится к лаконичному TypeScript-синтаксису. Вот как просто на TypeScript-е можно реализовать приведённую выше концепцию:
class Person {
private ID;
private familyName;
private familyNameSpell?; // опциональный параметр
private givenName?; // опциональный параметр
private givenNameSpell?; // опциональный параметр
public constructor(parametersObject: {
ID: string; // обязательный параметр - без него программа не скомпилируется
familyName: string; // обязательный параметр - без него программа не скомпилируется
familyNameSpell?: string; // опциональный параметр
givenName?: string; // опциональный параметр
givenNameSpell?: string;
}) {
this.ID = parametersObject.ID;
this.familyName = parametersObject.familyName;
this.familyNameSpell = parametersObject.familyNameSpell;
this.givenName = parametersObject.givenName;
this.givenNameSpell = parametersObject.givenNameSpell;
}
}
new Person({
ID: "1",
familyName: "毛", // если забыть обязательный параметр - TypeScript не скомпилируется
familyNameSpell : "Мао",
// ... опустил остальные опциональные параметры
})
Вторая реализация
Поразмыслив, я пришел к выводу, что в ParametersObject указывать опциональные параметры не нужно - мы можем их настроить уже в самом инстансе Person через цепные сеттеры. После этого оказалось, что класс ParametersObject не нужен - нужен только RequiredParameters.
public class Person {
private String ID;
private String familyName;
private String familyNameSpell;
private String givenName;
private String givenNameSpell;
public Person(RequiredParameters parametersObject) {
this.ID = parametersObject.ID;
this.familyName = parametersObject.familyName;
}
public String getID() { return this.ID; }
public String getFamilyName() {
return familyName;
}
public Person setFamilyName(String familyName) {
this.familyName = familyName;
return this;
}
public String getGivenName() { return givenName; }
public Person setGivenName(String givenName) {
this.givenName = givenName;
return this;
}
public String getFamilyNameSpell() { return familyNameSpell; }
public Person setFamilyNameSpell(String familyNameSpell) {
this.familyNameSpell = familyNameSpell;
return this;
}
public String getGivenNameSpell() {
return givenNameSpell;
}
public Person setGivenNameSpell(String givenNameSpell) {
this.givenNameSpell = givenNameSpell;
return this;
}
public static abstract class RequiredParameters {
public String ID;
public String familyName;
public abstract void setID();
public abstract void setFamilyName();
}
}
Теперь API стала немного лаконичнее:
new Person(
new Person.RequiredParameters() {
public void setID() { this.ID = "1"; }
public void setFamilyName() { this.familyName = "毛"; }
}
)
.setGivenName("澤東")
.setFamilyNameSpell("Мао")
.setGivenNameSpell("Цзэдун")