Ми зловживаємо статичними методами?


13

Пару місяців тому я почав працювати над новим проектом, і, переглядаючи код, це вразило мене кількістю застосованих статичних методів. collectionToCsvString(Collection<E> elements)В них зберігаються не тільки корисні методи як , але і велика кількість бізнес-логіки.

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

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Тепер хлопець сказав, що йому не подобається, що заняття, якими займатись Весною, зайво керуються, в основному тому, що це накладає обмеження, що клієнтські класи повинні бути самими весняними бобами. Ми закінчуємо тим, що все управляє Spring, що в значній мірі змушує нас процедурно працювати з об'єктами без громадянства. Більш-менш те, що зазначено тут https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Тож замість вищевказаного коду він має

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

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

new CustomerReceiptCreator().createReceipt()

Він стверджує, що статичні методи мають деякі додаткові переваги. А саме:

  • Легше читати. Імпортуйте статичний метод, і нам потрібно дбати лише про дію, не важливо, який клас це робить.
  • Це, очевидно, метод, що не містить дзвінків БД, настільки дешевий; і це добре, щоб це було зрозуміло, так що потенційний клієнт дійсно повинен зайти в код і перевірити це.
  • Простіше писати тести.

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

Отже, моє запитання полягає в тому, які потенційні підводні камені такого способу програмування?




4
staticМетод , який ви ілюструють вище просто звичайний фабричний метод. Зробити заводські методи статичними - це загальновизнана умова з ряду переконливих причин. Чи підходить заводський метод тут - інша справа.
Роберт Харві

Відповіді:


23

Яка різниця між new CustomerReceiptCreator().createReceipt()і CustomerReceiptCreator.createReceipt()? Досить нікого. Єдина істотна відмінність полягає в тому, що перший випадок має значно більш незграбний синтаксис. Якщо ви дотримуєтесь першого, переконавшись, що якимось чином уникнення статичних методів робить ваш код кращим ОО, ви сильно помиляєтесь. Створення об'єкта просто для виклику на ньому одного методу - це статичний метод за допомогою тупого синтаксису.

Де все стає іншим - це коли ви робите ін'єкцію, CustomerReceiptCreatorа не newін'єктуєте її. Розглянемо приклад:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

Порівняємо цю версію статичного методу:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

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

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

З іншого боку, що станеться, якщо CustomerReceiptCreatorраптом потрібна нова залежність? Скажімо, для цього потрібно перевірити прапор функції. Якби ми робили ін’єкцію, ми могли б зробити щось на кшталт:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Тоді ми закінчили, тому що викликовий код буде введений, CustomerReceiptCreatorякий автоматично отримає ін'єкцію a FeatureFlags.

Що робити, якщо ми використовували статичний метод?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Але зачекайте! код виклику також потрібно оновити:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

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

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

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