Класи корисності - це зло? [зачинено]


95

Я бачив цю нитку

Якщо клас "Службові програми" - це зло, куди я можу покласти свій загальний код?

і подумав, чому корисні класи є злом?

Скажімо, у мене є модель домену, яка містить десятки класів. Мені потрібно вміти екземпляри xml-ify. Чи слід робити метод toXml для батьків? Чи можу я створити допоміжний клас MyDomainXmlUtility.toXml? Це випадок, коли потреба бізнесу охоплює всю модель домену - чи справді вона належить як метод екземпляра? Що можна сказати, якщо у функціоналі xml програми є безліч допоміжних методів?


27
Девальвація терміна "зло" - це зло!
Matthew Lock,

1
@matthew я зберіг умови допису, на якому базується моє запитання ...;)
hvgotcodes

Класи корисності - погана ідея з тих самих причин, якими є одиночки.
CurtainDog

1
Дискусія щодо того, чи слід використовувати метод toXML, полягає в тому, що зосереджена на моделях доменів із розширеним та анемічним доменами. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
Джеймс П.

@james, toXML - це лише приклад ... а як щодо деяких функцій регулярного виразу, які використовуються повсюдно? Мовляв, вам потрібно зробити кілька речей із рядками у вашій моделі домену, але ви не можете використовувати підкласинг через іншу головну проблему, яка використовувала ваш один суперклас (у Java)
hvgotcodes

Відповіді:


127

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

Однак бувають випадки, коли ви можете використовувати утилітні класи для групування ряду методів разом - прикладом є java.util.Collectionsклас, який надає ряд утиліт, які можна використовувати в будь-якій колекції Java. Вони не є специфічними для одного конкретного типу Колекції, але натомість реалізують алгоритми, які можна використовувати в будь-якій Колекції.

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


добре поєднується з цією статтею про перевірку класу утиліти / допоміжного
засобу

8
Якщо мова не пропонує жодного механізму простору імен, крім класів, вам не залишається нічого іншого, як зловживати класом там, де це робить простір імен. У C ++ ви можете помістити окрему функцію, не пов’язану з іншими функціями, у простір імен. У Java ви повинні помістити його в клас як статичний (клас) член.
Невислаблена Моніка,

1
Групування як методи може бути канонічним з точки зору ООП. Однак OOP рідко є рішенням (серед винятків - архітектура високого рівня та набори віджетів, як один із випадків, коли неймовірно порушена семантика повторного використання коду шляхом успадкування насправді підходить) і загалом методи збільшують зв'язок та залежності. Тривіальний приклад: якщо ви надаєте для свого класу методи принтера / сканера як методи, ви поєднуєте клас із бібліотеками принтера / сканера. Крім того, ви фіксуєте кількість можливих реалізацій до фактично однієї. Якщо ви не вкинете інтерфейс і не збільшите залежності.
Джо Со

З іншого боку, я погоджуюся з вашими настроями щодо "групи за призначенням та функціональністю". Давайте ще раз переглянемо приклад принтера / сканера і почнемо з набору концертних класів, розроблених для загальної мети. Можливо, ви захочете написати деякий код налагодження та розробити текстове представлення для своїх класів. Ви можете реалізувати свої налагоджувальні принтери в одному файлі, який залежить від усіх цих класів та бібліотеки принтерів. Код без налагодження не буде обтяжений залежностями цієї реалізації.
Джо Со

1
Класи Util та Helper є невдалим артефактом таких обмежень, і ті, хто не розуміє ООП або не розуміє проблемний домен, продовжують писати процедурний код.
bilelovitch

56

Я думаю, що загальний консенсус полягає в тому, що класи корисності самі по собі не є злом . Вам просто потрібно використовувати їх розумно:

  • Розробіть статичні методи корисності загальними та багаторазовими. Переконайтесь, що вони не мають громадянства; тобто відсутні статичні змінні.

  • Якщо у вас є безліч корисних методів, розділіть їх на класи таким чином, щоб розробники полегшили їх пошук.

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

  • Для Java 8 і далі "методи за замовчуванням" в інтерфейсі можуть бути кращим варіантом, ніж класи службових програм. (Див ., Наприклад, Інтерфейс із методами за замовчуванням проти класу Abstract у Java 8 ).


Інший спосіб розглянути це Питання - це зауважити, що у цитованому Запитанні, "Якщо класи корисності є" злими "" , це аргумент "солом'яника". Це як я запитую:

"Якщо свині можуть літати, чи слід носити з собою парасольку?".

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

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


16

Класи корисності є проблематичними, оскільки вони не можуть згрупувати обов'язки з даними, які їх підтримують.

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

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


2
Я б назвав це практичною проблемою, оскільки, наприклад, у Java ви не можете створити жодної функції, яка не є методом у класі. У такій мові "клас без змінних і лише зі статичними методами" - це ідіома, яка позначає простір імен функцій утиліти ... Зіткнувшись із таким класом на Java, IMHO невірно і безглуздо говорити про клас , навіть якщо використовується classключове слово. Він шаркає, як простір імен, він ходить, як простір імен - це одне ...
Unslander Monica

2
Мені шкода, що я кажу прямо, але "статичні методи без стану [...] дивні у системі ООП [...] філософська проблема" вкрай хибні. ВЕЛИКО, якщо ви можете уникнути стану, оскільки це полегшує написання правильного коду. Це Зламано, коли мені доводиться писати, x.equals(y)тому що 1) той факт, що це насправді не повинно змінювати жодного стану, який би не передавався (і я не можу на нього покладатися, не знаючи реалізації) 2) Я ніколи не мав наміру ставити x" позиція "або" діяла "порівняно з y3) Я не хочу розглядати" особистість " xабо yпросто цікавлюсь їх цінностями.
Джо Со

4
Дійсно, більшість функціональних можливостей у реальному світі найкраще виражаються як статичні математичні функції, що не мають стану. Чи є Math.PI об’єктом, який слід мутувати? Чи справді мені доводиться створювати екземпляр AbstractSineCalculator, який реалізує IAbstractRealOperation, лише щоб отримати синус числа?
Джо Со

1
@JoSo Я повністю погоджуюся на цінність безгромадянства та прямих обчислень. Наївний ОО філософськи проти цього, і я думаю, що це робить його глибоким недоліком. Він заохочує абстрагувати речі, які в цьому не потребують (як ви вже продемонстрували), і не забезпечує значущих абстракцій для фактичного обчислення. Однак збалансований підхід до використання мови ОО прагне до незмінності та безгромадянства, оскільки вони сприяють модульності та ремонтопридатності.
Ален О'Ді

2
@ AlainO'Dea: Я усвідомлюю, що неправильно прочитав ваш коментар як позицію проти функціонування без громадянства. Прошу вибачення за свій тон - зараз я беру участь у своєму першому Java-проекті (після того, як я уникав ООП більшу частину свого 10-річного програмування), і я похований під шарами абстрактних речей з незрозумілим станом та значеннями. І мені просто потрібно свіжого повітря :-).
Джо Со

8

Я припускаю, що це починає ставати злим, коли

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

Але поки ці умови не виконуються, я вважаю, що вони дуже корисні.


"Присутні методи, які не повинні бути статичними методами." Як це можливо?
Koray Tugay

@KorayTugay Розглянемо нестатичне Widget.compareTo(Widget widget)та статичне WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Порівняння - це приклад того, чого не слід робити статично.
ndm13

5

Практичне правило

Ви можете розглянути цю проблему з двох точок зору:

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

Приклад 1. Правильне використання utilкласів / модулів. Приклад зовнішньої бібліотеки

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

введіть тут опис зображення


Приклад 2. Правильне використання utilкласів / модулів. Написання власного тексту utilбез виправдання іншим членам команди

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

Ось невеличка, але важлива деталь. Клас / модуль, який ви написали, не викликається HolidayUtil, але PolishHolidayCalculator. Функціонально це utilклас, але нам вдалося уникнути загального слова.

введіть тут опис зображення


1
YEd вентилятор я бачу;) Дивовижний додаток.
Шрідхар Сарнобат

3

Класи комунальних служб погані, тому що вони означають, що ви полінувались придумати кращу назву для класу :)

Сказано, я лінивий. Іноді вам просто потрібно виконати роботу, і ваш розум порожній. Ось тоді починають проникати класи "Утиліта".


3

Оглядаючись зараз на це питання, я б сказав, що методи розширення C # повністю знищують потребу в класах корисності. Але не всі мови мають таку геніальну конструкцію.

У вас також є JavaScript, де ви можете просто додати нову функцію прямо до існуючого об’єкта.

Але я не впевнений, що насправді існує елегантний спосіб вирішити цю проблему на більш старій мові, як C ++ ...

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

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


3

Я не зовсім згоден з тим, що корисні класи - це зло.

Хоча клас корисності може дещо порушувати принципів ОО, вони не завжди погані.

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

stl c ++ (станом на сьогодні) не підтримує це безпосередньо.

Ви можете створити поліморфне розширення std::string.

Але проблема в тому, чи дійсно ви хочете, щоб КОЖНИЙ рядок, який ви використовуєте у своєму проекті, був вашим розширеним класом рядків?

Бувають випадки, коли ОО насправді не має сенсу, і це один із них. Ми хочемо, щоб наша програма була сумісна з іншими програмами, тому ми будемо дотримуватися std::stringта створювати клас StringUtil_(або щось інше).

Я б сказав, що найкраще, якщо ви дотримуєтесь одного утиліту на клас. Я б сказав, що нерозумно мати один утиліт для всіх класів або багато утилів для одного класу.


2

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

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


2

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


1

З Java 8 ви можете використовувати статичні методи в інтерфейсах ... проблема вирішена.


Це не вирішує проблеми , викладені в stackoverflow.com/a/3340037/2859065
Luchostein

Чим це відрізняється від розміщення їх у класі та імпорту?
wilmol

1

Більшість класів Util погані, оскільки:

  1. Вони розширюють сферу застосування методів. Вони роблять код загальнодоступним, який інакше був би приватним. Якщо метод util потрібен кільком абонентам в окремих класах і є стабільним (тобто не потребує оновлення), на мій погляд, краще скопіювати та вставити приватні допоміжні методи у викличний клас. Коли ви виставляєте його як API, вам стає важче зрозуміти, що таке загальнодоступна точка входу до компонента jar (ви підтримуєте структуру дерева, що називається ієрархією, з одним із батьків на кожен метод. Це легше подумки розділити на компоненти, що важче, коли у вас є методи, що викликаються з кількох батьківських методів).
  2. Вони призводять до мертвого коду. Методи утиліти з часом стають невикористаними, оскільки ваша програма розвивається, і ви отримуєте невикористаний код, що забруднює вашу базу коду. Якби він залишився приватним, ваш компілятор сказав би вам, що метод не використовується, і ви можете просто видалити його (найкращий код - це взагалі ніякий код). Після того, як ви зробите такий метод неприватним, ваш комп’ютер буде безсилий, щоб допомогти вам видалити невикористаний код. Його можна викликати з іншого файлу jar, наскільки знає комп'ютер.

Є кілька аналогій із статичними та динамічними бібліотеками.


0

Коли я не можу додати метод до класу (скажімо, Accountблокується від змін розробниками-молодшими), я просто додаю кілька статичних методів до свого класу Utilities, наприклад так:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
Здається, ти для мене молодший ..: P Незлий спосіб це зробити - це ввічливо попросити свого «Розробника молодших класів» розблокувати Accountфайл. Або краще, скористайтеся неблокуючими системами управління джерелом.
Подіп 02

1
Я припускаю, що він мав на увазі, що Accountфайл заблоковано так, що молодші розробники не можуть вносити до нього зміни. Як молодший розробник, щоб обійти це, він зробив, як зазначено вище. Тим не менш, все-таки краще просто отримати доступ до запису до файлу і зробити це належним чином.
Doc

Додавання +1 до цього, оскільки голоси проти здаються жорсткими, коли ніхто не має повного контексту, чому ви надали цю відповідь
joelc

0

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

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