Java - чи погана ідея мати повністю статичні класи?


16

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

Наприклад, зараз мій клас кісток зберігає всі свої дані статично, і всі його методи також є статичними. Мені не потрібно його ініціалізувати, тому що коли я хочу закатати кістки і отримати нове значення, я просто використовую Dice.roll().

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

Мені було цікаво, чи вважається це "поганою практикою", коли мова йде про Java. З того, що я бачив, громада, схоже, поділяється на цю тему? У будь-якому разі, я хотів би, щоб обговорити це питання, і посилання на ресурси також були б чудовими!


1
Якщо ваша програма повністю процедурна. Чому ви вибрали Java?
Лаїв

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

@Laiv Я все-таки новачок у програмуванні, близько року C ++, я беру клас Java на цей семестр і мені починає більше подобатися Java, особливо графічні бібліотеки.
HexTeke


5
Щодо статичних класів. Якщо статичні методи чисті (утримуйте відсутність стану, майте аргументи введення та тип повернення). Не варто хвилюватися
Laiv

Відповіді:


20

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

Якщо Dice.roll()просто повертається нове випадкове число від 1 до 6, стан не змінюється. Зрозуміло, ви можете ділитися Randomекземпляром, але я б не вважав, що зміна стану, як за визначенням, вихід завжди буде добре, випадково. Він також захищений від потоку, тому тут проблем немає.

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

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


Що вважається зміною стану для класу? Ну, давайте виключаємо випадкові числа на секунду, оскільки вони недетерміновані за визначенням, і тому повернене значення часто змінюється.

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

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

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"

7
Якщо я хочу перевірити, що відбувається, коли подвійний шість прокатується, я застряг тут, як у вас статичний клас із генератором статичних випадкових чисел. Отже, ні, Dice.roll()не є дійсним винятком із правила "немає глобальної держави".
Девід Арно

1
@HexTeke Оновлена ​​відповідь.
Ніл

1
@DavidArno Щоправда, але я думаю, що в цей момент ми справді перебуваємо у біді, якщо ми тестуємо один дзвінок random.nextInt(6) + 1. ;)
Ніл

3
@Neil, вибачте, я не дуже добре пояснив себе. Ми не перевіряємо послідовність випадкових чисел, ми впливаємо на цю послідовність, щоб допомогти іншим тестам. Якщо ми тестуємо те, що, наприклад, RollAndMove()знову котиться, коли ми постачаємо подвійну шістку, то найпростіший і надійний спосіб зробити це - знущатися над або Diceгенератором випадкових чисел. Ерго, Diceне хоче стати статичним класом, використовуючи статичний випадковий генератор.
Девід Арно

12
Однак, ваше оновлення стосується проблеми: статичні методи повинні бути детермінованими; вони не повинні мати побічних ефектів.
Девід Арно

9

Навести приклад обмежень а static класу, що робити, якщо хтось із ваших гравців захоче отримати невеликий бонус на роликах? І вони готові платити великі гроші! :-)

Так, ви можете додати інший параметр Dice.roll(bonus) ,

Пізніше вам потрібні D20.

Dice.roll(bonus, sides)

Так, але деякі гравці мають "надзвичайно здатний" подвиг, тому вони ніколи не можуть "роздутися" (roll 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Це стає безладно, чи не так?


це здається ортогональним для питання, його стає безладним, чи це статичні методи, чи звичайні методи
jk.

4
@jk, я думаю, я зрозумів його суть. Немає сенсу мати статичний клас Dice, коли ви думаєте про більше типів кубиків. У такому випадку ми можемо мати різні об'єкти з кістки, моделюючи їх з хорошими методами ООП.
Дерік

1
@TimothyTruckle Я не заперечую це, все, що я говорю, це те, що ця відповідь насправді не відповідає на питання, тому що такий вид повзучості сфери не має нічого спільного з тим, що метод є статичним чи ні.
nvoigt

2
@nvoigt "Я не заперечую цього" - Ну, я. Найпотужнішою особливістю мови ОО є поліморфізм . А статичний доступ фактично заважає нам взагалі використовувати це. І ви маєте рацію: new Dice().roll(...)має бути concidered статичного доступу , а також Dice.roll(...). Ми отримуємо користь лише тоді, коли вводимо цю залежність.
Тімоті Тріклерл

2
@jk різниця полягає в тому, що staticбрудність відбувається в кожному дзвінку, а необхідні знання потрібні в кожному дзвінку, тому він всебічно плескається в усьому вашому додатку. У структурі OOP лише конструктор / завод повинен бути безладним, а деталі цього штампу інкапсульовані. Після цього використовуйте поліморфізм і просто телефонуйте roll(). Я можу відредагувати цю відповідь для уточнення.
user949300

3

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

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

Я не розробник Java, але думаю, що це буде значно важче зробити з повністю статичним класом Dice. Дивіться /programming/4482315/why-does-mockito-not-mock-static-methods


Тільки для записів: У Java у нас є PowerMock для заміни статичних залежностей, маніпулюючи байт-кодом. Але його використання - це просто здача поганого дизайну ...
Тімоті Тріклер

0

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

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

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