Статична фабрика проти фабрики як синглтон


24

У деяких кодах у мене є статична фабрика, подібна до цієї:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

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

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Кілька речей, які слід виділити:

  • Обидва заводи без громадянства.
  • Єдина відмінність між методами - їх сфери застосування (екземпляр проти статичних). Реалізації однакові.
  • Foo - боб, який не має інтерфейсу.

Аргументи, які я мав стати статичними, були:

  • Клас без громадянства, тому його не потрібно обґрунтовувати
  • Здається, більш природним є можливість викликати статичний метод, ніж інстанціювати завод

Аргументами для фабрики як сингтона були:

  • Добре все вводити
  • Незважаючи на стан без громадянства на заводі, тестування простіше за допомогою ін'єкцій (легко глузувати)
  • Слід знущатися над тестуванням споживача

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

Що думає громада? Хоча мені не подобається одинаковий підхід, я, здається, не маю надзвичайно сильних аргументів проти цього.


2
Завод чимось використовується . Щоб перевірити цю річ в ізоляції, вам потрібно надати їй макет своєї фабрики. Якщо ви не знущаєтесь над фабрикою, то помилка може спричинити невдалий тест одиниці, коли він не повинен.
Майк

Отже, ви виступаєте проти статичних комунальних підприємств взагалі чи просто статичних заводів?
bstempi

1
Я виступаю проти них взагалі. Наприклад, у C # DateTimeта Fileкласах, як відомо, важко перевірити саме з тих же причин. Якщо у вас є, наприклад, клас, який задає Createdдату DateTime.Nowв конструкторі, як ви збираєтеся створити одиничний тест з двома з цих об'єктів, створених за 5 хвилин один від одного? Що про роки нарізно? Ви дійсно не можете цього зробити (без великої роботи).
Майк

2
Чи не повинен ваш завод одноразово мати privateконструктор і getInstance()метод? Вибачте, незмінний збирач азоту!
TMN

1
@Mike - Погоджено - я часто використовував клас DateTimeProvider, який тривіально повертає DateTime.Now. Потім ви можете поміняти його на макет, який повертає заздалегідь визначений час, у ваших тестах. Це додаткова робота, яка передає її всім конструкторам - найбільший головний біль - це коли співробітники не купують на це 100% і не вказують явних посилань на DateTime.Зараз від лінь ... :)
Джулія Хейвард

Відповіді:


15

Навіщо ви відокремлюєте свою фабрику від об'єктного типу, який вона створює?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

Це те, що Джошуа Блох описує як свій пункт 1 на сторінці 5 своєї книги "Ефективна Java". Java достатньо багатослівний без додавання зайвих класів та синглів та чогось іншого. Є деякі випадки, коли окремий заводський клас має сенс, але їх небагато і між ними.

Перефразовуючи пункт 1 Джошуа Блоха, на відміну від конструкторів, статичні заводські методи можуть:

  • Майте імена, які можуть описувати конкретні види створення об’єктів (Bloch використовує ймовірнуPrime (int, int, Random) як приклад)
  • Повернути існуючий об’єкт (подумайте: легковагова вага)
  • Повернути підтип початкового класу або інтерфейс, який він реалізує
  • Зменшити багатослівність (із зазначенням параметрів типу - див. Блок Блок)

Недоліки:

  • Класи без загальнодоступних або захищених конструкторів не можуть бути підкласами (але ви можете використовувати захищені конструктори та / або пункт 16: користь композиції над спадщиною)
  • Статичні заводські методи не відрізняються від інших статичних методів (використовуйте стандартну назву типу () або valueOf ())

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


Я теж думав про це, і думаю, що мені це подобається. Я не пам'ятаю, чому я розділяв їх в першу чергу.
bstempi

Взагалі кажучи, ми погоджуємось із стилем Блоха. Зрештою, ми збираємося перенести наші заводські методи в клас, який вони створюють. Єдина відмінність нашого рішення від вашого полягає в тому, що конструктор залишатиметься загальнодоступним, щоб люди могли все-таки безпосередньо інстанціювати клас. Дякую за вашу відповідь!
bstempi

Але ... це жахливо. Ви, швидше за все, хочете класу, який реалізує IFoo та передав його. Використовуючи цей шаблон, це вже неможливо; Ви повинні жорстко кодувати IFoo, щоб означати Foo, щоб отримати екземпляри. Якщо у вас є IFooFactory, який містить IFoo create (), клієнтському коду не потрібно знати деталі реалізації того, який конкретний Foo був обраний як IFoo.
Вільберт

@Wilbert У public static IFoo of(...) { return new Foo(...); } чому проблема? Блох-списки, що задовольняють вашу стурбованість, є однією з переваг цієї конструкції (друга точка кулі вище). Я не повинен розуміти ваш коментар.
GlenPeterson

Щоб створити екземпляр, ця конструкція очікує: Foo.of (...). Однак існування Foo ніколи не слід піддавати впливу, тільки IFoo повинен бути відомий (як Foo - це лише конкретний імпульс IFoo). Наявність статичного фабричного методу на конкретному імпульсі порушує це і призводить до зв'язку між клієнтським кодом та конкретною реалізацією.
Вільберт

5

Ось у верхній частині голови, ось деякі проблеми зі статикою на Java:

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

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

  • поля статики досить схожі на глобальні


Ви згадали:

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

Що викликає цікавість запитати вас:

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

Гей, дякую за відповідь! У тому порядку, який ви запитували: (1) Перевагами статичних методів є синтаксичний цукор та відсутність зайвого екземпляра. Приємно читати код, який має статичний імпорт, і говорити щось на кшталт Foo f = createFoo();, ніж мати ім'я з ім'ям. Сам екземпляр не надає ніякої корисності. (2) Я так думаю. Він був розроблений, щоб подолати той факт, що багато з цих методів не існує в Stringкласі. Замінити щось настільки фундаментальне, як Stringце не є варіантом, тому утиліти - це шлях. (продовження)
bstempi

Вони прості у використанні і не вимагають від вас жодної справи. Вони відчувають себе природними. (3) Оскільки моя фабрика дуже схожа на заводські методи всередині Integer. Вони дуже прості, мають дуже мало логіки і є лише для того, щоб забезпечити зручність (наприклад, немає жодної іншої причини, щоб вони мали їх). Основна частина мого аргументу полягає в тому, що вони покладаються на відсутність стану - немає підстав ізолювати їх під час тестів. Люди не знущаються StringUtilsпід час тестування - навіщо їм знущатися над цим простим заводом зручності?
bstempi

3
Вони не знущаються, StringUtilsоскільки є обґрунтованою впевненістю, що методи там не мають побічних ефектів.
Роберт Харві

@RobertHarvey Я припускаю, що я висловлюю те саме твердження про свою фабрику - вона нічого не змінює. Так само, як StringUtilsповертає якийсь результат без зміни входів, так і моя фабрика нічого не змінює.
bstempi

@bstempi Я там збирався трохи за методом Сократа. Я здогадуюсь, що я смоктав це.

0

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

Уявіть, що ваша програма розвивається і тепер у деяких випадках вам потрібно створити FooBar(підклас Foo). Тепер ви повинні пройти через всі місця в вашому коді , які знають про ваш SomeFactoryі написати якусь - то логіку , яка дивиться на someConditionі виклики SomeFactoryабо SomeOtherFactory. Насправді, дивлячись на ваш приклад код, це гірше. Ви плануєте просто додати метод за методом для створення різних Foos, і весь код вашого клієнта повинен перевірити умову, перш ніж з'ясувати, який Foo зробити. Це означає, що всі клієнти повинні мати глибокі знання про те, як працює Ваша Фабрика, і всі вони повинні змінюватися, коли потрібен (або видалений!) Новий Foo.

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

Вам може бути цікаво, як це відбувається у виробничому коді. Щоб надати вам один приклад із кодової бази, над якою я працюю, що починає вирівнюватися після трохи більше року закрутки проектів, у мене є купа Фабрик, які використовуються для створення об’єктів даних запитань (вони є чимось на зразок презентаційних моделей ). У мене це QuestionFactoryMapв основному хеш для пошуку, який заводський питання використовувати, коли. Отже, щоб створити програму, яка задає різні запитання, я поклав на карту різні Фабрики.

Питання можуть бути представлені або в Інструкціях, або вправах (які обидва реалізують IContentBlock), тому кожне InstructionsFactoryабо ExerciseFactoryотримає копію QuestionFactoryMap. Потім різні фабрики з інструкціями та вправами передаються тому, ContentBlockBuilderщо знову підтримує хеш, який слід використовувати, коли. І тоді, коли завантажується XML, ContentBlockBuilder викликає Фабрику вправ або інструкцій із хеша і запитує його createContent(), а потім Фабрика делегує побудову питань, що витягує із свого внутрішнього QuestionFactoryMap, виходячи з того, що знаходиться у кожному вузлі XML проти хеша. На жаль , ця річ є BaseQuestionзамість цьогоIQuestion, але це просто показує, що навіть коли ви обережно ставитесь до дизайну, ви можете робити помилки, які можуть переслідувати вас вниз.

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


Подумайте на хвилину фабричних методів у Integerкласі - прості речі, такі як перетворення з String. Ось що і Fooробить. Моє Fooне призначене для подовження (моя вина за те, що я не заявив про це), тому у мене немає ваших поліморфних питань. Я розумію цінність наявності поліморфних заводів і використовували їх у минулому ... Я просто не думаю, що вони тут доречні. Чи можете ви уявити собі, якщо вам доведеться запровадити фабрику, щоб виконувати прості операції Integer, які зараз вводяться через статичні заводи?
bstempi

Крім того, ви припущення щодо програми досить виправдані. Додаток досить задіяний. Це Foo, однак, набагато простіше , ніж багато хто з класів. Це просто простий POJO.
bstempi

Якщо Foo мається на увазі розширення, то це право є підставою для фабрики бути екземпляром - ви надаєте правильний екземпляр фабрики, щоб дати вам правильний підклас, а не викликати if (this) then {makeThisFoo()} else {makeThatFoo()}все через ваш код. Одне, що я помітив у людей з хронічно поганою архітектурою, - це те, що вони схожі на людей, що переживають хронічний біль. Вони насправді не знають, що таке відчуття, як не боліти, тому біль, в якому вони перебувають, не здається такою поганою.
Емі Бланкенсіп

Крім того, ви можете перевірити це посилання про проблеми зі статичними методами (навіть математичними!)
Емі Бланкенсіп

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