Java - это плохая идея иметь полностью статические классы?

16

Я работаю над большим сольным проектом и сейчас, и у меня есть несколько классов, в которых я не вижу причин для создания экземпляра.

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

У меня есть несколько похожих классов, у которых есть только одна основная функция, подобная этой, и я собираюсь начать работать над своего рода «контроллерным» классом, который будет отвечать за все события (например, когда игрок движется и какой текущий ход). это так) и я обнаружил, что могу следовать той же идее для этого класса. Я никогда не планирую создавать несколько объектов для этих конкретных классов, так что было бы плохой идеей сделать их полностью статичными?

Мне было интересно, если это считается "плохой практикой", когда речь идет о Java. Из того, что я видел, сообщество, похоже, немного расколото по этой теме? В любом случае, я хотел бы обсудить это, и ссылки на ресурсы тоже были бы великолепны!

HexTeke
источник
1
Если ваша программа полностью процедурная. Почему вы выбрали Java?
Laiv
12
Попробуйте написать модульные тесты для этих классов, и вы поймете, почему людям не нравятся статические методы, которые обращаются к статическому состоянию.
Джори Себрехтс
@Laiv Я все еще новичок в программировании, около года на C ++, в этом семестре я беру Java-класс и начинаю любить Java гораздо больше, особенно графические библиотеки.
HexTeke
5
По поводу статических классов. Если статические методы чистые (не содержат состояния, имеют входные аргументы и возвращаемый тип). Не о чем беспокоиться
Laiv

Ответы:

20

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

Если Dice.roll()просто возвращается новое случайное число от 1 до 6, это не меняет состояние. Конечно, вы можете делиться Randomэкземпляром, но я бы не подумал, что изменение состояния, как по определению, вывод всегда будет хорошим, случайным. Это также потокобезопасный, поэтому здесь нет проблем.

Вы часто будете видеть финальные "вспомогательные" или другие служебные классы, которые имеют приватный конструктор и статические члены. Закрытый конструктор не содержит логики и служит только для предотвращения создания экземпляра класса. Последний модификатор лишь возвращает идею, что это не тот класс, из которого вы бы хотели получить. Это просто служебный класс. Если все сделано правильно, не должно быть ни одного сингла или других учеников, которые сами не являются статичными и окончательными.

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


Что считается изменением состояния для класса? Что ж, давайте исключим случайные числа на секунду, так как они по определению недетерминированы и, следовательно, возвращаемое значение часто меняется.

Чистая функция - это функция, которая является детерминированной, то есть для данного входа вы получите один и ровно один выход. Вы хотите, чтобы статические методы были чистыми функциями. В 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
@ Нил, извиняюсь, я не очень хорошо себя объяснил. Мы не проверяем последовательность случайных чисел, мы влияем на эту последовательность, чтобы помочь другим тестам. Если мы тестируем, что, например, RollAndMove()снова бросаемся, когда мы поставляем двойную шестерку, то самый простой и надежный способ сделать это - смоделировать либо Diceгенератор случайных чисел. Ergo, Diceне хочет быть статическим классом, использующим статический генератор случайных чисел.
Дэвид Арно
12
Хотя ваше обновление затрагивает проблему: статические методы должны быть детерминированными; у них не должно быть побочных эффектов.
Дэвид Арно
9

Чтобы привести пример ограничений staticкласса, что если некоторые из ваших игроков захотят получить небольшой бонус за бросок кубика? И они готовы платить большие деньги! :-)

Да, вы можете добавить еще один параметр, поэтому Dice.roll(bonus),

Позже вам нужны D20s.

Dice.roll(bonus, sides)

Да, но у некоторых игроков есть умение «чрезвычайно способного», поэтому они никогда не могут «шарить» (бросьте 1).

Dice.roll(bonus, sides, isFumbleAllowed),

Это становится грязным, не так ли?

user949300
источник
это кажется ортогональным к вопросу, становится беспорядочным, являются ли это статическими методами или нормальными методами
jk.
4
@jk, думаю, я понял его точку зрения. Не имеет смысла иметь статический класс Dice, когда вы думаете о большем количестве кубиков. В этом случае мы можем иметь разные объекты для игры в кости, моделируя их с помощью хороших ООП-практик.
Дерик
1
@TimothyTruckle Я не оспариваю это, все, что я говорю, - то, что этот ответ фактически не отвечает на вопрос, потому что этот вид ползучести области действия не имеет никакого отношения к статическому методу или нет.
nvoigt
2
@nvoigt "Я не оспариваю это" - Ну, я делаю. Самая мощная особенность ОО-языка - полиморфизм . И статический доступ фактически не позволяет нам использовать это вообще. И вы правы: new Dice().roll(...)должен быть принят статический доступ, а также Dice.roll(...). Мы только выиграем , если впрыснуть эту зависимость.
Тимоти Траклле
2
@jk разница в том, что staticв каждом вызове возникает беспорядок , а необходимые знания требуются при каждом вызове, поэтому он распространяется по всему приложению. В структуре ООП только конструктор / фабрика должны быть грязными, а детали этого кристалла инкапсулированы. После этого используйте полиморфизм и просто позвоните roll(). Я мог бы отредактировать этот ответ, чтобы уточнить.
user949300
3

Я думаю, что в конкретном случае класса Dice использование методов экземпляра, а не статики, значительно упростит тестирование.

Если вы хотите выполнить модульное тестирование чего-либо, использующего экземпляр Dice (например, класс Game), то из ваших тестов вы можете внедрить некоторую форму двойного теста Dice, которая всегда возвращает фиксированную последовательность значений. Ваш тест может проверить, что игра имеет правильный результат для этих бросков костей.

Я не Java-разработчик, но я думаю, что было бы значительно сложнее сделать это с полностью статическим классом Dice. См. Https://stackoverflow.com/questions/4482315/why-does-mockito-not-mock-static-methods

bdsl
источник
Только для записей: в Java у нас есть PowerMock для замены статических зависимостей путем манипулирования байтовым кодом. Но использовать его - это просто
отказ
0

На самом деле это известно как шаблон Monostate , где каждый экземпляр (и даже «без экземпляра») делится своим статусом. Каждый член является членом класса (то есть, нет членов экземпляра). Они обычно используются для реализации классов «инструментария», которые связывают набор методов и констант, связанных с одной ответственностью или требованием, но не нуждаются в состоянии для работы (они чисто функциональны). Фактически, Java поставляется с некоторыми из них в комплекте (например, Math ).

Немного не по теме: я редко согласен с именованием ключевых слов в VisualBasic, но в этом случае, я думаю, sharedэто, безусловно, яснее и семантически лучше (оно распределяется между самим классом и всеми его экземплярами), чем static(остается после жизненного цикла области, в которой он объявлен).

Хесус Алонсо Абад
источник