Як інша популярна мова уникає необхідності використовувати заводський зразок, керуючи подібними складностями, як у Java / Java EE?


23

Фабрична модель (або принаймні використання FactoryFactory..) - це приклад багатьох жартів, як тут .

Крім того, що мають багатослівні та "креативні" назви, такі як RequestProcessorFactoryFactory.RequestProcessorFactory , чи є щось принципово неправильне з заводською схемою, якщо вам потрібно програмувати в Java / C ++ і чи є примірник для Abstract_factory_pattern ?

Як інша популярна мова (наприклад, Ruby чи Scala ) уникає необхідності користуватися нею при керуванні подібною складністю?

Причину, про яку я питаю, я бачу здебільшого критику заводів, згаданих у контексті екосистеми Java / Java EE , але вони ніколи не пояснюють, як інші мови / рамки їх вирішують.



4
Проблема не у використанні фабрик - це ідеально витончена модель. Саме надмірне використання фабрик особливо поширене в підприємницькому світі Java.
CodesInChaos

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

Відповіді:


28

Ваше запитання позначено "Java", не дивно, що ви запитуєте, чому знущається з "Фабричного шаблону": сама Java надходить з добре упакованими зловживаннями цього шаблону.

Наприклад, спробуйте завантажити XML-документ із файлу та запустіть запит XPath проти нього. Вам потрібно щось на зразок 10 рядків коду просто для налаштування Фабрик та будівельників:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

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

Оскільки ви запитуєте, яка альтернатива, тут ви завантажуєте XML-файл у C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Я підозрюю, що новоспечені розробники Java бачать безумство Фабрики і думаю, що це нормально - якщо генії, які самі побудували Яву, так багато їх використали.

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


1
Лише зауважте, що ваша C # альтернатива використовує новіший LINQ до XML - старіша альтернатива DOM виглядала б приблизно так: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Загалом, LINQ до XML набагато простіше у використанні, хоча переважно в інших областях.) І ось стаття KB Я знайшов метод, рекомендований для MS: support.microsoft.com/kb/308333
Боб

36

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

І це, без сумніву, випливає з того, як навчають програмуванню (і моделям). Школярам (які часто називають себе "учнями") кажуть "створити X за допомогою шаблону Y", і після кількох ітерацій, які вважають, що це спосіб підійти до будь-якої проблеми програмування. Кульмінацією цього стала система, в якій я мав невдоволення підтримувати, яку створив один такий (чоловік навіть мав кілька книг про об'єктно-орієнтований дизайн до свого імені та викладацьку посаду на кафедрі ЦС великого університету ).
Тож вони починають застосовувати певний зразок, який їм подобається в школі проти будь-якого і всього, незалежно від того, підходить він чи ні.

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

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

У системі була нормалізована база даних до 5-ї нормальної форми (я вам цього не знаю).

Система, яка по суті була трохи більше, ніж адресна книга, яка могла використовуватися для реєстрації та відстеження вхідних та вихідних документів, мала кодову базу даних 100 МБ у C ++ та понад 50 таблиць баз даних. Друк 500 форм листів займе 72 години за допомогою принтера, підключеного безпосередньо до комп'ютера, на якому працює програмне забезпечення.

Ось чому люди знущаються над моделями, а конкретно з людьми, що однозначно зосереджуються на одному конкретному шаблоні.


40
Інша причина полягає в тому, що деякі зразки банди чотирьох - це справді багатослівні хаки для речей, які можна зробити тривіально мовою з першокласними функціями, що робить їх неминучими анти-шаблонами. "Стратегія", "Спостерігач", "Фабрика", "Команда" і "Метод шаблону" - це хаки для передачі функцій в якості аргументів; "Відвідувач" - це хак, щоб виконати відповідність шаблону за типом / варіантом / тегом об'єднання. Той факт, що деякі люди прихильно ставляться до чогось, що буквально тривіально у багатьох інших мовах, - це те, що змушує людей знущатися над C ++, Java та подібними мовами.
Доваль

7
Дякую за цей анекдот. Чи можете ви також трохи детальніше зупинитися на тому, "які альтернативи?" частина питання, щоб ми теж не стали подібними?
Філіпп

1
@Doval Чи можете ви сказати мені, як ви повертаєте значення з виконаної команди або викликаної стратегії, коли ви можете передати лише функцію? І якщо ви говорите про закриття, то пам’ятайте, що це саме те саме, що і створення класу, крім більш чіткого.
Ейфорія

2
@Euphoric Я не бачу проблеми, ви можете детальніше розглянути? У будь-якому випадку, вони не зовсім те саме. Можна також сказати, що об'єкт з одним полем точно такий же, як вказівник / посилання на змінну, або ціле число точно таке ж, як (реальне) перерахунок. Існують відмінності в практиці: немає сенсу порівнювати функції; Я ще не бачив стислого синтаксису для анонімних занять; і всі функції з тими ж аргументами та типи повернення мають один і той же тип (на відміну від класів / інтерфейсів з різними іменами, які є різними типами, навіть якщо вони однакові)
Doval,

2
@Euphoric Правильно. Це вважатиметься поганим функціональним стилем, якщо є спосіб зробити роботу без побічних ефектів. Найпростішою альтернативою було б використання об'єднання типу / позначення типу для повернення одного з N видів значень. Незважаючи на це, припустимо, що практичного шляху немає; це не те саме, що клас. Це насправді інтерфейс (у значенні OOP.) Не важко зрозуміти, чи вважаєте ви, що будь-яка пара функцій з однаковими підписами була б взаємозамінною, тоді як два класи ніколи не є взаємозамінними, навіть якщо вони мають абсолютно однаковий зміст.
Doval

17

Заводи мають багато переваг, що дозволяють зробити елегантний дизайн додатків у деяких ситуаціях. Одне полягає в тому, що ви можете встановити властивості об’єктів, які пізніше ви хочете створити в одному місці, створивши фабрику, а потім передати цю фабрику навколо. Але часто вам насправді цього не потрібно робити. У такому випадку використання заводу просто додає додаткових складностей, фактично не даючи нічого взамін. Візьмемо, наприклад, цю фабрику:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

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

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Але коли надмірна інженерія - це ваша проблема, заміна Заводу на будівельника, швидше за все, не буде значною мірою поліпшенням.

Найпростіша заміна для будь-якого шаблону - це, звичайно, створення об’єктів-об'єктів за допомогою простого конструктора з newоператором:

Widget widget = new ColoredWidget(COLOR_RED);

Однак у конструкторів є важливий недолік у більшості об'єктно-орієнтованих мов: вони повинні повернути об'єкт саме цього класу і не можуть повернути підтип.

Коли вам потрібно вибрати підтип під час виконання, але не хочете вдаватися до створення цілого нового класу Builder або Factory, для цього ви можете використовувати заводський метод. Це статичний метод класу, який повертає нові екземпляри цього класу або одного з його підкласів. Завод, який не підтримує внутрішнього стану, часто може бути замінений таким фабричним методом:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Новою особливістю Java 8 є посилання на методи, які дозволяють вам передавати методи так, як ви це робили з заводом без громадянства. Зручно, що все, що приймає посилання на метод, також приймає будь-який об'єкт, який реалізує той самий функціональний інтерфейс, який також може бути повноцінною Фабрикою з внутрішнім станом, тому ви можете легко вводити заводи пізніше, коли побачите причину для цього.


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

1
спасибі (+1), але відповідь не пояснює, як інші PL можуть вирішити це, проте дякую, що вказав на посилання на метод Java 8.
senseiwu

1
Я часто думав, що в об'єктно-орієнтованих рамках було б сенс обмежувати фактичну конструкцію об'єкта саме типом, і це було foo = new Bar(23);б рівнозначно foo = Bar._createInstance(23);. Об'єкт повинен мати можливість надійно запитувати власний тип за допомогою приватного getRealType()методу, але об’єкти повинні мати можливість призначити супертип, до якого потрібно повернути зовнішні виклики getType(). Зовнішній код не має байдуже, чи закликає new String("A")насправді повертати екземпляр String[на відміну від, наприклад, екземпляра SingleCharacterString].
supercat

1
Я використовую багато заводів статичних методів, коли мені потрібно вибрати підклас на основі контексту, але відмінності повинні бути непрозорими для абонента. Наприклад, у мене є ряд класів зображень, які містять, draw(OutputStream out)але генерують дещо інший html, завод статичних методів створює правильний клас для ситуації, і тоді абонент може просто використовувати метод малювання.
Майкл Шопсін

1
@supercat, можливо, вам буде цікаво поглянути на aim-c. Там побудова об'єктів зазвичай складається з простих методів викликів, як правило [[SomeClass alloc] init]. Можна повернути зовсім інший екземпляр класу "або інший об'єкт (наприклад, кешоване значення)
axelarge

3

Фабрики того чи іншого виду знаходяться майже в будь-якій об'єктно-орієнтованій мові за відповідних обставин. Іноді просто потрібен спосіб вибрати тип об’єкта для створення на основі простого параметра, такого як рядок.

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

Що мене вразило, коли я дізнався Scala, і я не знаю Ruby, але вважаю, що це так само, це те, що мова є достатньо виразною, що програмісти не завжди намагаються підштовхнути "сантехнічну" роботу до зовнішніх файлів конфігурації . Замість того, щоб спокуситись створити фабричні фабрики, щоб з'єднати свої класи разом у різних конфігураціях, у Scala ви використовуєте об'єкти із змішаними ознаками. Це також відносно просто створити прості DSL-мови на мові для завдань, які часто перероблені на Java.

Крім того, як вказували інші коментарі та відповіді, закриття та функції першого класу усувають потребу у багатьох моделях. З цієї причини, я вірю, що багато анти-шаблонів почнуть зникати, коли C ++ 11 і Java 8 набули широкого поширення.


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

Я думаю, якщо Scala наздожене Java, то це буде тому, що люди віддають перевагу "Scala шляху" архітектури програмного забезпечення. Мені здається, що Java вважає більш ймовірним те, що Java продовжує застосовувати більше функцій, які спонукають людей переходити на Scala, як, наприклад, дженерики Java 5 та лямбди та потоки Java 8, що, сподіваємось, врешті-решт, програмісти відмовляться від анти-шаблонів, що виникли, коли вони функції були недоступні Завжди знайдуться прихильники FactoryFactory, якими б хорошими мовами не користувалися. Очевидно, деякі люди люблять ці архітектури, або вони не були б такими поширеними.
Карл Білефельдт

2

Взагалі, існує така тенденція, що програми Java жахливо перероблені [потрібне цитування]. Наявність багатьох фабрик - один із найпоширеніших симптомів надмірної інженерії; саме тому люди висміюються над цим.

Зокрема, проблема, яку Java має з фабриками, полягає в тому, що в Java а) конструктори не є функціями, а б) функції не є першокласними громадянами.

Уявіть, що ви можете написати щось подібне (нехай це Functionбуде відомий інтерфейс JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Дивіться, немає фабричних інтерфейсів чи класів. Багато динамічні мови мають першокласні функції. На жаль, це неможливо з Java 7 або раніше (реалізація того ж з фабриками залишається читачем як вправа).

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


2
це навіть не намагається відповісти на поставлене запитання: "які альтернативи?"
гнат

1
Прочитайте повз другий абзац, і ви перейдете до частини "як це роблять інші мови". (PS: інша відповідь також не містить альтернатив)
Головоногі

4
@gnat Чи не він опосередковано говорить, що альтернативою є використання першокласних функцій? Якщо ви хочете, це чорна скринька, яка може створювати об’єкти, ваші параметри - це або фабрика, або функція, а фабрика - це лише маска.
Doval

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

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