Створення та знищення Java-об’єктів

Ця глава з книги

Ця глава з книги

Цей розділ з книги 

Пункт 2: Розглянемо конструктора, коли стикаємося з багатьма параметрами конструктора

Статичні заводи та конструктори поділяють обмеження: вони погано масштабуються до великої кількості необов’язкових параметрів. Розглянемо випадок із класом, який представляє ярлик “Факти харчування”, який з’являється на упакованих продуктах. Ці етикетки містять декілька обов’язкових полів - розмір порції, порції в упаковці та калорії на порцію - і понад двадцять необов’язкових полів - загальний жир, насичені жири, трансжир, холестерин, натрій тощо. Більшість продуктів мають ненульові значення лише для кількох із цих необов’язкових полів.

коли

Який тип конструкторів чи статичних фабрик ви повинні написати для такого класу? Традиційно програмісти застосовують шаблон телескопічного конструктора, в якому ви надаєте конструктор лише необхідних параметрів, інший - з одним необов’язковим параметром, третій - з двома необов’язковими параметрами тощо, завершуючись конструктором із усіма необов’язковими параметрами. Ось як це виглядає на практиці. Для стислості показано лише чотири необов’язкові поля:

Коли ви хочете створити екземпляр, ви використовуєте конструктор із найкоротшим списком параметрів, що містить усі параметри, які ви хочете встановити:

Зазвичай для цього виклику конструктора потрібно багато параметрів, які ви не хочете встановлювати, але ви все одно змушені передавати значення для них. У цьому випадку ми передали значення 0 для жиру. З «лише» шістьма параметрами це може здатися не так погано, але швидко виходить з-під контролю, оскільки кількість параметрів збільшується.

Коротко, шаблон телескопічного конструктора працює, але важко написати код клієнта, коли є багато параметрів, і важче все-таки його прочитати. Читач залишається здивованим, що означають усі ці значення, і повинен ретельно підрахувати параметри, щоб з’ясувати це. Довгі послідовності однаково набраних параметрів можуть спричинити незначні помилки. Якщо клієнт випадково скасує два таких параметри, компілятор не скаржиться, але програма буде поводитися неправильно під час виконання (Елемент 40).

Другою альтернативою, коли ви стикаєтеся з багатьма параметрами конструктора, є шаблон JavaBeans, в якому ви викликаєте безпараметричний конструктор для створення об'єкта, а потім викликаєте методи встановлення для встановлення кожного необхідного параметра та кожного необов'язкового параметра, що цікавить:

Ця схема не має жодних недоліків конструкції телескопічного конструктора. Створити екземпляри легко, хоча і трохи багатослівно, і легко прочитати отриманий код:

На жаль, шаблон JavaBeans має свої серйозні недоліки. Оскільки конструкція розділена на кілька дзвінків, JavaBean може перебувати в суперечливому стані ще на етапі його побудови. Клас не має можливості забезпечити узгодженість лише шляхом перевірки правильності параметрів конструктора. Спроба використовувати об’єкт, коли він перебуває у несумісному стані, може спричинити помилки, віддалені від коду, що містить помилку, і тому важко налагодити. Пов’язаний недолік полягає в тому шаблон JavaBeans виключає можливість зробити клас незмінним (Пункт 15), і вимагає додаткових зусиль з боку програміста для забезпечення безпеки потоків.

Можна зменшити ці недоліки шляхом ручного «заморожування» об’єкта, коли його конструкція завершена і не дозволяючи використовувати його до морозу, але цей варіант є громіздким і рідко використовується на практиці. Більше того, це може спричинити помилки під час виконання, оскільки компілятор не може переконатися, що програміст викликає метод freeze на об'єкті перед його використанням.

На щастя, є третя альтернатива, яка поєднує безпеку шаблону телескопічного конструктора та читабельність шаблону JavaBeans. Це форма шаблону Builder [Gamma95, с. 97]. Замість того, щоб робити бажаний об'єкт безпосередньо, клієнт викликає конструктор (або статичну фабрику) з усіма необхідними параметрами і отримує об'єкт будівельника. Потім клієнт викликає сетероподібні методи об’єкта конструктора для встановлення кожного необов’язкового параметра, що цікавить. Нарешті, клієнт викликає безпараметричний метод побудови для генерації об’єкта, який є незмінним. Конструктор - це статичний клас-член (Елемент 22) класу, який він будує. Ось як це виглядає на практиці:

Зверніть увагу, що NutritionFacts є незмінним, і що всі значення параметрів за замовчуванням знаходяться в одному місці. Методи сеттера будівельника повертають самого конструктора, щоб можна було викликати ланцюжки викликів. Ось як виглядає код клієнта:

Цей клієнтський код легко писати і, що ще важливіше, читати. Шаблон Builder імітує названі додаткові параметри як це знайдено в Аді та Пітоні.

Як і конструктор, будівельник може накладати інваріанти на його параметри. Метод побудови може перевірити ці інваріанти. Дуже важливо, щоб їх перевіряли після копіювання параметрів з конструктора на об'єкт, і щоб вони перевірялись у полях об'єкта, а не в полях конструктора (Пункт 39). Якщо будь-які інваріанти порушені, метод побудови повинен викинути IllegalStateException (Елемент 60). Метод деталізації винятку повинен вказувати, який інваріант порушено (Пункт 63).

Інший спосіб накласти інваріанти, що включають декілька параметрів, полягає в тому, щоб методи встановлення приймали цілі групи параметрів, для яких повинен міститись якийсь інваріант. Якщо інваріант не задовольняється, метод setter викидає IllegalArgumentException. Це має ту перевагу, що виявляє інваріантну помилку, як тільки передаються недійсні параметри, замість того, щоб чекати на виклик побудови.

Незначною перевагою будівельників перед конструкторами є те, що будівельники можуть мати кілька параметрів varargs. Конструктори, як і методи, можуть мати лише один параметр varargs. Оскільки розробники використовують окремі методи для встановлення кожного параметра, вони можуть мати скільки завгодно параметрів varargs, до одного на кожен метод сетеру.

Шаблон Builder є гнучким. Один конструктор може бути використаний для побудови декількох об'єктів. Параметри конструктора можна змінювати між створеннями об'єктів, щоб змінювати об'єкти. Конструктор може заповнювати деякі поля автоматично, наприклад, серійний номер, який автоматично збільшується кожного разу, коли створюється об’єкт.

Конструктор, параметри якого були встановлені, робить прекрасну Фабрику абстрактних [Gamma95, с. 87]. Іншими словами, клієнт може передати такий конструктор методу, щоб дозволити методу створити один або кілька об'єктів для клієнта. Щоб увімкнути це використання, вам потрібен тип, який представляє конструктор. Якщо ви використовуєте випуск 1.5 або пізніший випуск, для всіх будівельників достатньо одного загального типу (Елемент 26), незалежно від того, який тип об’єкта вони будують:

Зверніть увагу, що наш клас NutritionFacts.Builder може бути оголошений для реалізації Builder .

Методи, які приймають екземпляр Builder, як правило, обмежують параметр типу будівельника, використовуючи обмежений тип підстановки (Елемент 28). Наприклад, ось метод, який створює дерево за допомогою наданого клієнтом екземпляра Builder для побудови кожного вузла:

Традиційна реалізація абстрактної фабрики в Java є об'єктом класу, при цьому метод newInstance відіграє роль методу побудови. Це використання загрожує проблемами. Метод newInstance завжди намагається викликати конструктор без параметрів класу, який може навіть не існувати. Ви не отримуєте помилку під час компіляції, якщо клас не має доступного конструктора без параметрів. Натомість клієнтський код повинен справлятися з InstantiationException або IllegalAccessException під час виконання, що є потворно і незручно. Крім того, метод newInstance поширює будь-які винятки, викинуті конструктором без параметрів, навіть якщо newInstance не має відповідних пропозицій throws. Іншими словами, Class.newInstance порушує перевірку винятків під час компіляції. Інтерфейс Builder, показаний вище, виправляє ці недоліки.

Шаблон Builder має свої недоліки. Для того, щоб створити об'єкт, спочатку потрібно створити його конструктор. Хоча витрати на створення конструктора навряд чи будуть помітні на практиці, це може бути проблемою в деяких критичних ситуаціях. Крім того, шаблон Builder є більш багатослівним, ніж шаблон телескопічного конструктора, тому його слід використовувати лише за умови достатньої кількості параметрів, скажімо, чотирьох або більше. Але майте на увазі, що можливо, ви захочете додати параметри в майбутньому. Якщо ви починаєте з конструкторів або статичних фабрик і додаєте конструктор, коли клас еволюціонує до такої міри, що кількість параметрів починає виходити з-під контролю, застарілі конструктори або статичні фабрики будуть стирчати як хворий палець. Тому часто краще починати спочатку з будівельника.

Підсумовуючи, Шаблон Builder є хорошим вибором при розробці класів, конструктори або статичні фабрики яких мають більше кількох параметрів, особливо якщо більшість із цих параметрів є необов’язковими. Клієнтський код набагато легше читати та писати у конструкторах, ніж із традиційним шаблоном телескопічного конструктора, а будівельники набагато безпечніші, ніж JavaBeans.