Как элегантно инициализировать классы с большим количеством полей?
В моем приложении я должен создать множество различных типов объектов. Каждый тип содержит несколько полей и должен быть добавлен к содержащему типу. Как я могу сделать это элегантным способом?
Мой текущий шаг инициализации выглядит примерно так:
public void testRequest() {
//All these below used classes are generated classes from xsd schema file.
CheckRequest checkRequest = new CheckRequest();
Offers offers = new Offers();
Offer offer = new Offer();
HotelOnly hotelOnly = new HotelOnly();
Hotel hotel = new Hotel();
Hotels hotels = new Hotels();
Touroperator touroperator = new Touroperator();
Provider provider = new Provider();
Rooms rooms = new Rooms();
Room room = new Room();
PersonAssignments personAssignments = new PersonAssignments();
PersonAssignment personAssignment = new PersonAssignment();
Persons persons = new Persons();
Person person = new Person();
Amounts amounts = new Amounts();
offers.getOffer().add(offer);
offer.setHotelOnly(hotelOnly);
room.setRoomCode("roomcode");
rooms.getRoom().add(room);
hotels.getHotel().add(hotel);
hotel.setRooms(rooms);
hotelOnly.setHotels(hotels);
checkRequest.setOffers(offers);
// ...and so on and so on
}
Я действительно хочу избежать написания кода, подобного этому, потому что немного беспорядочно создавать экземпляры каждого объекта отдельно, а затем инициализировать каждое поле несколькими строками кода (например, вызывать new Offer()
а потом setHotelOnly(hotelOnly)
а потом add(offer)
).
Какие элегантные методы я могу использовать вместо того, что у меня есть? Есть ли "Factories
"что можно использовать? У вас есть ссылки / примеры, чтобы избежать написания такого кода?
Я действительно заинтересован в реализации чистого кода.
Контекст:
Я разрабатываю RestClient
Приложение для отправки почтовых запросов на веб-сервис.
API представлен в виде xsd schema
файл, и я создал все объекты с JAXB
Перед отправкой запроса мне нужно создать множество объектов, потому что они имеют зависимости друг от друга. (В предложении есть отели, в отеле есть номера, в номере есть люди... И эти классы являются сгенерированными)
Спасибо за вашу помощь.
6 ответов
Вы можете использовать конструктор или шаблон компоновщика, или вариацию шаблона компоновщика, чтобы решить проблему наличия слишком большого количества полей на этапе инициализации.
Я собираюсь немного расширить ваш пример, чтобы доказать свою точку зрения, почему эти опции полезны.
Понимание вашего примера:
Скажем Offer
это просто контейнерный класс для 4 полей:
public class Offer {
private int price;
private Date dateOfOffer;
private double duration;
private HotelOnly hotelOnly;
// etc. for as many or as few fields as you need
public int getPrice() {
return price;
}
public Date getDateOfOffer() {
return dateOfOffer;
}
// etc.
}
В вашем примере, чтобы установить значения для этих полей, вы используете сеттеры:
public void setHotelOnly(HotelOnly hotelOnly) {
this.hotelOnly = hotelOnly;
}
К сожалению, это означает, что если вам нужно предложение со значениями во всех полях, вы должны сделать то, что у вас есть:
Offers offers = new Offers();
Offer offer = new Offer();
offer.setPrice(price);
offer.setDateOfOffer(date);
offer.setDuration(duration);
offer.setHotelOnly(hotelOnly);
offers.add(offer);
Теперь давайте посмотрим на улучшение этого.
Вариант 1: Конструкторы!
Конструктор, отличный от конструктора по умолчанию (конструктор по умолчанию в настоящее время Offer()
) полезно для инициализации значений полей в вашем классе.
Версия Offer
использование конструкторов будет выглядеть так:
public class Offer {
private int price;
private Date dateOfOffer;
//etc.
// CONSTRUCTOR
public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
this.price = price;
this.dateOfOffer = dateOfOffer;
//etc.
}
// Your getters and/or setters
}
Теперь мы можем инициализировать его в одну строку!
Offers offers = new Offers();
Offer offer = new Offer(price, date, duration, hotelOnly);
offers.add(offer);
Даже лучше, если вы никогда не используете offer
кроме этой единственной строки: offers.add(offer);
Вам даже не нужно сохранять его в переменной!
Offers offers = new Offers();
offers.add( new Offer(price, date, duration, hotelOnly) ); // Works the same as above
Вариант 2: шаблон строителя
Шаблон построителя полезен, если вы хотите иметь возможность иметь значения по умолчанию для любого из ваших полей.
Проблема, которую решает шаблон компоновщика, заключается в следующем беспорядочном коде:
public class Offer {
private int price;
private Date dateOfOffer;
// etc.
// The original constructor. Sets all the fields to the specified values
public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
this.price = price;
this.dateOfOffer = dateOfOffer;
// etc.
}
// A constructor that uses default values for all of the fields
public Offer() {
// Calls the top constructor with default values
this(100, new Date("10-13-2015"), 14.5, new HotelOnly());
}
// A constructor that uses default values for all of the fields except price
public Offer(int price) {
// Calls the top constructor with default values, except price
this(price, new Date("10-13-2015"), 14.5, new HotelOnly());
}
// A constructor that uses default values for all of the fields except Date and HotelOnly
public Offer(Date date, HotelOnly hotelOnly) {
this(100, date, 14.5, hotelOnly);
}
// A bunch more constructors of different combinations of default and specified values
}
Видишь, как это может запутаться?
Шаблон строителя - это еще один класс, который вы помещаете в свой класс.
public class Offer {
private int price;
// etc.
public Offer(int price, ...) {
// Same from above
}
public static class OfferBuilder {
private int buildPrice = 100;
private Date buildDate = new Date("10-13-2015");
// etc. Initialize all these new "build" fields with default values
public OfferBuilder setPrice(int price) {
// Overrides the default value
this.buildPrice = price;
// Why this is here will become evident later
return this;
}
public OfferBuilder setDateOfOffer(Date date) {
this.buildDate = date;
return this;
}
// etc. for each field
public Offer build() {
// Builds an offer with whatever values are stored
return new Offer(price, date, duration, hotelOnly);
}
}
}
Теперь вам не нужно иметь столько конструкторов, но вы все равно сможете выбирать, какие значения вы хотите оставить по умолчанию, а какие хотите инициализировать.
Offers offers = new Offers();
offers.add(new OfferBuilder().setPrice(20).setHotelOnly(hotelOnly).build());
offers.add(new OfferBuilder().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200).build());
offers.add(new OfferBuilder().build());
Это последнее предложение просто одно со всеми значениями по умолчанию. Другие значения по умолчанию, кроме тех, которые я установил.
Видишь, как это облегчает жизнь?
Вариант 3: Вариация шаблона Builder
Вы также можете использовать шаблон компоновщика, просто заставив ваши текущие сеттеры вернуть тот же объект Offer. Это точно так же, кроме как без дополнительного OfferBuilder
учебный класс.
Предупреждение. Как указывает WW пользователя ниже, эта опция нарушает JavaBeans - стандартное соглашение по программированию для контейнерных классов, таких как Offer. Таким образом, вы не должны использовать это в профессиональных целях и должны ограничивать свое использование в своих собственных практиках.
public class Offer {
private int price = 100;
private Date date = new Date("10-13-2015");
// etc. Initialize with default values
// Don't make any constructors
// Have a getter for each field
public int getPrice() {
return price;
}
// Make your setters return the same object
public Offer setPrice(int price) {
// The same structure as in the builder class
this.price = price;
return this;
}
// etc. for each field
// No need for OfferBuilder class or build() method
}
И ваш новый код инициализации
Offers offers = new Offers();
offers.add(new Offer().setPrice(20).setHotelOnly(hotelOnly));
offers.add(new Offer().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200));
offers.add(new Offer());
Это последнее предложение просто одно со всеми значениями по умолчанию. Другие значения по умолчанию, кроме тех, которые я установил.
Таким образом, несмотря на большую работу, если вы хотите очистить этап инициализации, вам нужно использовать один из этих параметров для каждого из ваших классов, в которых есть поля. Затем используйте методы инициализации, которые я включил в каждый метод.
Удачи! Нужно ли что-нибудь из этого для дальнейшего объяснения?
Я всегда предпочитал использовать конструктор шаблонов с поворотом, потому что он предоставляет гораздо больше, чем базовый подход шаблона компоновщика.
Но что происходит, когда вы хотите сказать пользователю, что он должен вызвать один метод конструктора или другой, поскольку это важно для класса, который вы пытаетесь построить.
Подумайте о компоновщике для компонента URL. Как можно подумать о методах компоновщика для инкапсуляции доступа к атрибутам URL, одинаково ли они важны, взаимодействуют ли они друг с другом и т. Д.? В то время как параметры запроса или фрагмент не являются обязательными, имя хоста не является; Вы могли бы сказать, что протокол также требуется, но для этого у вас может быть значимое значение по умолчанию, например http, верно?
В любом случае, я не знаю, имеет ли это смысл для вашей конкретной проблемы, но я подумал, что стоит упомянуть, чтобы другие посмотрели на нее.
Некоторые хорошие ответы уже даны здесь!
То, что пришло мне на ум в качестве дополнения, это Domain Driven Design. Определенная часть строительных блоков, с Entity, Value Object, Aggregate, Factory и т. Д.
Хорошее введение дано в домене, управляемом дизайном - быстро (pdf).
В идеале, объект не должен заботиться о создании своих зависимостей. Следует беспокоиться только о вещах, которые он должен делать с ними. Рассматривали ли вы какие-либо рамки внедрения зависимостей? Spring или Google's Juice довольно универсальны и занимают мало места.
Идея проста: вы объявляете зависимости и позволяете фреймворку решать, когда / как / где их создавать и "внедрить" его в ваши классы.
Если вы не хотите использовать какой-либо фреймворк, вы можете взять у него заметки по дизайну и попытаться эмулировать их шаблоны проектирования и настроить его для своего варианта использования.
Кроме того, вы можете до некоторой степени упростить вещи, правильно используя Коллекции. Например, что делает дополнительная функция Offers
есть кроме хранения коллекции Offer
? Я не уверен, каковы ваши ограничения, но, если вы сможете сделать эту часть немного чище, вы получите огромный выигрыш во всех местах, где вы создаете экземпляры объектов.
Я просто предоставляю этот ответ, потому что он был упомянут в комментарии, и я думаю, что он также должен быть частью этого перечисления шаблонов проектирования.
Шаблон проектирования пустых объектов
умысел
Цель Null Object - инкапсулировать отсутствие объекта, предоставляя заменяемую альтернативу, которая предлагает подходящее поведение по умолчанию, которое ничего не делает. Одним словом, дизайн, в котором "из ничего ничего не выйдет"
Используйте шаблон Null Object, когда
- объект требует коллаборациониста. Шаблон Null Object не представляет это сотрудничество - он использует сотрудничество, которое уже существует
- некоторые экземпляры соавтора не должны ничего делать
- Вы хотите абстрагировать обработку нуля от клиента
Здесь вы найдете полную часть шаблона проектирования "Null Object"
Dozer Framework предоставляет хороший способ скопировать значения из объекта ws в ваш dto. Вот еще один пример. Кроме того, если имена получателей / сеттеров одинаковы для обоих классов, вам не нужен специальный конвертер