Приборкання класів «функції корисних функцій»


15

У нашій кодовій базі Java я постійно бачу таку схему:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

Мене хвилює те, що я маю проходити екземпляр FooUtilскрізь, бо тестування.

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

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

Чи є спосіб вирішити це краще, що я не бачу?

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


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

4
Чому ці методи не є членами класу, даними якими вони маніпулюють?
Жуль

3
Майте окремий набір Junit, який викликає статичний fns, якщо FooUtility. Тоді ви можете використовувати його без глузування. Якщо це щось, що вам потрібно знущатись, я підозрюю, що це не просто утиліта, а якась IO або бізнес-логіка, і насправді має бути посиланням на члена класу та ініціалізуватися методом конструктора, інітату або сетера.
tgkprog

2
+1 для коментаря Жуля. Класи утилів - це кодовий запах. Залежно від семантики повинно бути краще рішення. Можливо, FooUtilметоди слід вводити FooSomething, можливо, є властивості, FooSomethingякі слід змінити / розширити, так що FooUtilметоди більше не потрібні. Надайте, будь ласка, конкретніший приклад того, що FooSomethingнам допомогти, даючи хорошу відповідь на це питання.
валентери

13
@valenterry: Єдина причина, що класи утилітів - запах коду, полягає в тому, що Java не забезпечує хорошого способу самостійного функціонування. Є дуже вагомі причини для функцій, не характерних для класів.
Роберт Харві

Відповіді:


16

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

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

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

Моя пропозиція - перегорнути свої колекції (або боби) з будь-якими іншими тісно пов’язаними даними в нові класи та помістити "корисний" код у фактичні об'єкти.

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

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

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


Ця відповідь настільки приголомшлива. У мене була ця проблема (очевидно), я прочитав цю відповідь з деякою сумнівністю, повернувся назад і подивився на мій код і запитав "який об’єкт відсутній, який повинен мати ці методи". Ось, знайдеться елегантне рішення та заповнено прогалину між об'єктами та моделлю.
GreenAsJade


@Deduplicator За 40 років програмування я рідко (ніколи?) Не бачив випадків, коли мене блокувало занадто багато рівнів непрямості (окрім випадкової боротьби з моїм IDE, щоб перейти до реалізації, а не інтерфейсу), але я ' часто (Дуже часто) бачили випадок, коли люди писали жахливий код, а не створювали явно потрібний клас. Хоча я вважаю, що занадто багато непрямості могло вкусити деяких людей, я б помилявся на стороні належних принципів ОО, таких як кожен клас добре робив щось, належне стримування тощо.
Білл К

@BillK Взагалі гарна ідея. Але без виходу люка інкапсуляція справді може перешкодити. І є певна краса у вільних алгоритмах, з якими ви можете підходити до будь-якого типу, хоч, правда, у строгих OO мовах нічого, окрім OO, досить незручно.
Deduplicator

@Deduplicator Під час написання "утиліти" Функції, які повинні бути записані за межами конкретних бізнес-вимог, ви правильні. Класи утиліти, повні функцій (наприклад, колекції), просто незручні в ОО, оскільки вони не пов'язані з даними. це "Escape Hatch", який люди, яким не зручно користуватися OO (також, постачальники інструментів повинні використовувати їх для дуже загальної функціональності). Моя пропозиція вище полягала в тому, щоб зафіксувати ці хаки в класах, коли ви працюєте за бізнес-логікою, щоб ви могли знову пов’язати свій код зі своїми даними.
Білл К

1

Ви можете використовувати шаблон постачальника і виконання вашого FooSomethingз FooUtilProviderоб'єктом (може бути в конструкторі, тільки один раз).

FooUtilProviderматиме тільки один метод: FooUtils get();. Потім клас використовуватиме будь-який FooUtilsпримірник, який він надає.

Тепер це один крок від використання рамки введення залежностей, коли вам доведеться лише двічі провести провід для коінтейнера DI: для виробничого коду та для набору тестів. Ви просто прив'язуєте FooUtilsінтерфейс до або, RealFooUtilsабо MockedFooUtilsінше відбувається автоматично. Кожен об'єкт, від якого залежить, FooUtilsProviderотримує належну версію FooUtils.

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