Конструктори проти заводських методів [закрито]


181

При моделюванні класів, який є кращим способом ініціалізації:

  1. Конструктори, або
  2. Заводські методи

І які б були міркування щодо використання будь-якого з них?

У певних ситуаціях я вважаю за краще використовувати заводський метод, який повертає null, якщо об'єкт неможливо побудувати. Це робить код акуратним. Я можу просто перевірити, чи повернене значення не є нульовим перед тим, як робити альтернативні дії, на відміну від викидання виключення з конструктора. (Я особисто не люблю винятків)

Скажімо, у мене є конструктор класу, який очікує значення id. Конструктор використовує це значення для заповнення класу з бази даних. У випадку, коли запису із вказаним ідентифікатором не існує, конструктор видає RecordNotFoundException. У цьому випадку мені доведеться докласти конструкцію всіх таких класів у блоці спробувати ..

На відміну від цього, я можу мати статичний заводський метод для тих класів, який поверне нуль, якщо запис не буде знайдено.

Який підхід у цьому випадку кращий, конструктор чи заводський метод?

Відповіді:


66

Зі сторінки 108 Шаблони дизайну: Елементи багаторазового об’єктно-орієнтованого програмного забезпечення від Gamma, Helm, Johnson та Vlissides.

Використовуйте шаблон заводського методу, коли

  • клас не може передбачити клас об’єктів, який він повинен створити
  • клас хоче, щоб його підкласи вказували об’єкти, які він створює
  • класи делегують відповідальність одному з кількох допоміжних підкласів, і ви хочете локалізувати знання того, який допоміжний підклас є делегатом

21
Статичний заводський метод відрізняється від схеми дизайну GoF - Factory Factory Pattern. stackoverflow.com/questions/929021/…
Sree Rama

Будь ласка, не порівнюйте з фабричним методом схеми дизайну GoF.
Sree Rama

137
це мені нічого не пояснює
Сушант

@Sushant, чому так?
PaulD

2
Ця відповідь не відповідає на питання, просто передає інформацію для читання, щоб зрозуміти / пояснити концепцію ... Це більше коментаря.
Crt

202

Запитайте себе, що вони є, і чому ми їх маємо. Вони обоє є для створення примірника об'єкта.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Поки що різниці немає. Тепер уявімо, що у нас є різні типи шкіл, і ми хочемо перейти від використання ElementarySchool до HighSchool (який походить від ElementarySchool або реалізує той самий інтерфейс ISchool, що і ElementarySchool). Зміна коду буде:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

У випадку інтерфейсу у нас буде:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Тепер, якщо у вас є цей код у декількох місцях, ви можете бачити, що використання заводського методу може бути досить дешевим, оскільки після зміни фабричного методу ви закінчите (якщо ми будемо використовувати другий приклад з інтерфейсами).

І в цьому головна відмінність і перевага. Коли ви починаєте мати справу зі складною ієрархією класів і хочете динамічно створити екземпляр класу з такої ієрархії, ви отримаєте наступний код. Тоді фабричні методи можуть прийняти параметр, який вказує методу, який конкретний екземпляр потрібно інстанціювати. Скажімо, у вас клас MyStudent, і вам потрібно створити відповідний об’єкт ISchool, щоб ваш учень був членом цієї школи.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Тепер у вашому додатку є одне місце, яке містить ділову логіку, яка визначає, який об’єкт ISchool подавати для різних об’єктів IStudent.

Отже - для простих класів (об'єктів значення тощо) конструктор просто чудовий (ви не хочете перенапружувати свою програму), але для складних ієрархій класів заводський метод є кращим способом.

Таким чином ви слідуєте першому принципу дизайну - від групи чотирьох книг "Програма до інтерфейсу, а не реалізації".


2
Навіть коли ви думаєте, що це простий клас, є ймовірність, що комусь потрібно розширити ваш простий клас, тому заводський метод все-таки краще. Наприклад, ви можете почати з ElementarySchool, але пізніше хтось (включаючи себе) може поширити його на PrivateElementarSchool та PublicElementarSchool.
джек

10
це має бути прийнята відповідь
am05mhz

2
@David, хороша відповідь, але чи можна розширити на прикладі, коли для кожної реалізації інтерфейсу можуть знадобитися різні параметри для побудови. Ось дурний приклад: IFood sandwich = new Sandwich(Cheese chz, Meat meat);і IFood soup = new Soup(Broth broth, Vegetable veg);як тут може допомогти фабрика та / або будівельник?
Брайан

1
Я щойно прочитав три інші пояснення щодо використання фабрики, і саме це, нарешті, було для мене «клацанням». Дякую!
Даніель Пейрано

Чому на це не приймається відповідь?
Томаш

74

Вам потрібно прочитати (якщо у вас є доступ) Ефективна Java 2 Пункт 1: Розгляньте статичні заводські методи замість конструкторів .

Переваги статичних заводських методів:

  1. Вони мають імена.
  2. Вони не вимагають створювати новий об'єкт кожного разу, коли вони викликаються.
  3. Вони можуть повернути об'єкт будь-якого підтипу свого типу повернення.
  4. Вони знижують багатослівність створення екземплярів параметризованого типу.

Недоліки статичних заводських методів:

  1. Надаючи лише статичні заводські методи, класи без загальнодоступних або захищених конструкторів не можна підкласифікувати.
  2. Вони не відрізняються від інших статичних методів

4
Це здається мені досить серйозною помилкою в Java, тоді загальною проблемою з OOD. Є численні мови OO, які навіть не мають конструкторів, але підкласинг працює чудово.
Йорг W Міттаг

1
@cherouvim чому здебільшого код пишеться за допомогою Конструкторів, якщо( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Асиф Муштак

Хороші бали. Хоча це специфічно для Java. Можна створити випадок мовної функції, яка дозволяє відрізняти фабричні методи від інших статичних методів.
OCDev

30

За замовчуванням слід віддати перевагу конструкторам, оскільки їх простіше зрозуміти та записати. Однак, якщо вам спеціально потрібно відокремити привабливості конструкції об'єкта від його семантичного значення, як його розуміє клієнтський код, вам краще використовувати фабрики.

Різниця між конструкторами і заводами аналогічна, скажімо, змінній та покажчику на змінну. Є ще один рівень непрямості, який є недоліком; але є й інший рівень гнучкості, що є перевагою. Отож, роблячи вибір, вам було б радимо зробити цю вартість порівняно з аналізом вигоди.


17
Отже, (стиль TDD) ви б почали з конструкторів як найпростішого способу виконання роботи. А потім рефактор на заводах, як тільки ви починаєте отримувати запахи коду (як повторна умовна логіка, яка визначає, який конструктор викликати)?
AndyM

1
Дуже важливий момент. Дослідження користувача, що порівнює фабрики та конструктори, виявило дуже вагомі результати, що вказують на те, що фабрикам шкодить зручність використання API: "користувачам потрібно значно більше часу (p = 0,005), щоб побудувати об'єкт із заводом, ніж з конструктором" [Шаблон заводу в дизайні API : Оцінка зручності використання ].
mdeff

12

Використовуйте фабрику лише тоді, коли вам потрібен додатковий контроль зі створенням об'єктів, таким чином, який не вдалося зробити з конструкторами.

Фабрики мають можливість кешування, наприклад.

Ще один спосіб використання заводів - це сценарій, коли ви не знаєте тип, який ви хочете побудувати. Часто ви бачите такий тип використання у фабричних сценаріях плагінів, де кожен плагін повинен виходити з базового класу або реалізовувати якийсь інтерфейс. Фабрика створює екземпляри класів, які походять від базової версії або що реалізують інтерфейс.


11

Цитата з "Ефективна Java", 2-е видання, пункт 1: Розгляньте статичні заводські методи замість конструкторів, с. 5:

"Зауважте, що статичний заводський метод не є тим самим, як заводський метод з шаблонів проектування [Gamma95, стор. 107]. Статичний заводський метод, описаний у цьому пункті, не має прямого еквівалента в шаблонах проектування."


10

Окрім "ефективної Java" (як згадується в іншій відповіді), ще одна класична книга пропонує:

Віддайте перевагу статичним заводським методам (з іменами, що описують аргументи) перевантаженим конструкторам.

Напр. не пиши

Complex complex = new Complex(23.0);

але замість цього пишіть

Complex complex = Complex.fromRealNumber(23.0);

Книга іде так далеко, щоб запропонувати зробити Complex(float)конструктор приватним, щоб змусити користувача викликати статичний заводський метод.


2
Читання тієї частини книги привело мене сюди
Purple Haze

1
@Bayrem: Я теж недавно перечитував це і думав, що слід додати його до відповідей.
blue_note

1
На відповідну записку, може виявитися корисним деякі угоди про іменуванні виробленого java.time рамки щодо найменування from…, to…, parse…, with…, і так далі. Майте на увазі, що класи java.time створені таким чином, щоб вони були незмінні, але деякі з цих імен можуть бути корисними і для змінних класів.
Василь Бурк

7

Конкретний приклад програми CAD / CAM.

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

Форму можна було б зробити за допомогою фабрики. Оскільки, хоча існує клас фігур, кожна форма буде налаштована по-різному, залежно від типу типу. Ми не знаємо, яку форму ми будемо ініціалізувати, поки користувач не зробить вибір.


5

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

Цей процес безумовно повинен бути поза конструктором.

  1. Конструктор не повинен мати доступ до бази даних.

  2. Завдання та причина конструктора - ініціалізація членів даних та встановлення інваріанта класу за допомогою значень, переданих у конструктор.

  3. Для всього іншого кращим підходом є використання статичного фабричного методу або в більш складних випадках окремий заводський чи будівельний клас.

Деякі керівні лінії від конструктора від Microsoft :

Робіть мінімальну роботу в конструкторі. Конструктори не повинні робити більше роботи, крім захоплення параметрів конструктора. Витрати на будь-яку іншу обробку слід відкладати, поки цього не потрібно.

І

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


2

Іноді доводиться перевіряти / обчислювати деякі значення / умови під час створення об'єкта. І якщо він може кинути Виняток - constructro - це дуже поганий спосіб. Тому вам потрібно зробити щось подібне:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

Де всі додаткові обчислення знаходяться в init (). Але тільки ви як розробник справді знаєте про це init (). І звичайно, через місяці ти просто забудеш про це. Але якщо у вас є фабрика - просто виконайте все, що вам потрібно в одному методі, приховуючи цей init () від прямого виклику - так що проблем не буде. При такому підході не виникає проблем із падінням на створення та витік пам'яті.

Хтось сказав вам про кешування. Це добре. Але ви також повинні пам’ятати про Flyweight візерунок, який приємно використовувати з заводським способом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.