Java - czy to zły pomysł, aby mieć w pełni statyczne klasy?

16

Obecnie pracuję nad większym projektem solo i mam kilka klas, w których nie widzę powodu, aby tworzyć instancję.

Na przykład moja klasa kości przechowuje teraz statycznie wszystkie dane, a wszystkie metody są również statyczne. Nie muszę go inicjować, ponieważ kiedy chcę rzucić kostką i uzyskać nową wartość, po prostu używam Dice.roll().

Mam kilka podobnych klas, które mają tylko jedną taką główną funkcję i zamierzam rozpocząć pracę nad rodzajem „kontrolera”, który będzie odpowiedzialny za wszystkie zdarzenia (np. Kiedy gracz się poruszy i jaka tura to jest) i odkryłem, że mógłbym zastosować ten sam pomysł w tej klasie. Nigdy nie planuję tworzenia wielu obiektów dla tych konkretnych klas, więc czy złym pomysłem byłoby uczynienie ich w pełni statycznymi?

Zastanawiałem się, czy jest to uważane za „złą praktykę”, jeśli chodzi o Javę. Z tego, co widziałem, społeczność wydaje się być podzielona na ten temat? W każdym razie chciałbym trochę o tym dyskutować, a linki do zasobów też byłyby świetne!

HexTeke
źródło
1
Jeśli twój program jest w pełni proceduralny. Dlaczego wybrałeś Javę?
Laiv
12
Spróbuj napisać testy jednostkowe dla tych klas, a dowiesz się, dlaczego ludzie nie lubią metod statycznych uzyskujących dostęp do stanu statycznego.
Joeri Sebrechts
@Laiv Nadal jestem trochę nowy w programowaniu, około roku C ++, biorę klasę Java w tym semestrze i zaczynam bardziej lubić Javę, szczególnie biblioteki graficzne.
HexTeke
5
Odnośnie klas statycznych. Jeśli metody statyczne są czyste (nie posiadają stanu, mają argumenty wejściowe i typ zwracany). Nie ma się czym martwić
Laiv

Odpowiedzi:

20

Nie ma nic złego w klasach statycznych, które są naprawdę statyczne . Innymi słowy, nie istnieje żaden stan wewnętrzny, który mógłby zmienić dane wyjściowe metod.

Jeśli Dice.roll()po prostu zwraca nowy losowy numer od 1 do 6, to nie zmienia stanu. To prawda, że ​​współdzielisz Randominstancję, ale nie uważam, że zmiana stanu z definicji zawsze będzie dobra, losowa. Jest również bezpieczny dla wątków, więc nie ma tu żadnych problemów.

Często zobaczysz ostatecznego „Pomocnika” lub inne klasy narzędzi, które mają prywatnego konstruktora i członków statycznych. Prywatny konstruktor nie zawiera logiki i służy jedynie do zapobiegania tworzeniu instancji klasy przez kogoś. Ostatni modyfikator sprowadza ten pomysł do domu, że nie jest to klasa, z której nigdy nie chciałbyś czerpać. To tylko klasa użyteczności. Jeśli zostanie to wykonane poprawnie, nie powinno być singletonów ani innych członków klasy, którzy sami nie są statyczni i ostateczni.

Dopóki postępujesz zgodnie z tymi wytycznymi i nie tworzysz singli, nie ma w tym absolutnie nic złego. Wspominasz o klasie kontrolera, a to prawie na pewno będzie wymagało zmian stanu, więc odradzam stosowanie tylko metod statycznych. Możesz w dużej mierze polegać na statycznej klasie narzędzi, ale nie możesz uczynić go statyczną klasą narzędzi.


Co uważa się za zmianę stanu klasy? Cóż, wykluczmy liczby losowe na sekundę, ponieważ z definicji nie są deterministyczne, a zatem wartość zwracana często się zmienia.

Czysta funkcja to taka, która jest deterministyczna, co oznacza, że ​​dla danego wejścia otrzymasz jeden i dokładnie jeden wynik. Chcesz, aby metody statyczne były czystymi funkcjami. W Javie istnieją sposoby poprawiania zachowania metod statycznych w celu utrzymania stanu, ale prawie nigdy nie są to dobre pomysły. Kiedy zadeklarujesz metodę jako statyczną , typowy programista natychmiast przyjmie, że jest to czysta funkcja. Odbiegając od oczekiwanego zachowania, ogólnie rzecz biorąc, masz tendencję do tworzenia błędów w swoim programie i należy tego unikać.

Singleton jest klasą zawierającą metody statyczne, które są jak najbardziej przeciwne „czystej funkcji”. Pojedynczy statyczny element prywatny jest przechowywany wewnętrznie w klasie, która służy do zapewnienia, że ​​istnieje dokładnie jedna instancja. To nie jest najlepsza praktyka i może sprawić kłopoty później z wielu powodów. Aby dowiedzieć się, o czym mówimy, oto prosty przykład singletona:

// 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"
Neil
źródło
7
Jeśli chcę przetestować, co się stanie, gdy zostanie wyrzucona podwójna szóstka, utknąłem tutaj, ponieważ masz klasę statyczną ze statycznym generatorem liczb losowych. Zatem nie, Dice.roll()nie jest prawidłowym wyjątkiem od reguły „brak stanu globalnego”.
David Arno,
1
@HexTeke Zaktualizowana odpowiedź.
Neil
1
@DavidArno To prawda, ale przypuszczam, że w tym momencie naprawdę mamy kłopoty, jeśli testujemy pojedyncze połączenie z random.nextInt(6) + 1. ;)
Neil
3
@Neil, przepraszam, nie wyjaśniłem się zbyt dobrze. Nie testujemy losowej sekwencji liczb, wpływamy na tę sekwencję, aby pomóc innym testom. Jeśli testujemy, że np. RollAndMove()Rzuci się ponownie, gdy dostarczymy podwójną szóstkę, to najłatwiejszym, najsolidniejszym sposobem, aby to zrobić, jest wykpienie jednego z nich Dicelub generatora liczb losowych. Ergo, Dicenie chce być klasą statyczną, używając statycznego generatora losowego.
David Arno,
12
Twoja aktualizacja uderza jednak w sedno: metody statyczne powinny być deterministyczne; nie powinny mieć skutków ubocznych.
David Arno,
9

Aby dać przykład ograniczeń staticklasy, co jeśli niektórzy z twoich graczy chcą uzyskać niewielką premię za rzuty kostkami? I są gotowi zapłacić duże pieniądze! :-)

Tak, można dodać inny parametr, tak Dice.roll(bonus),

Później potrzebujesz D20.

Dice.roll(bonus, sides)

Tak, ale niektórzy gracze mają atut „wyjątkowo sprawny”, więc nigdy nie mogą „zmarnować” (rzucić 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Robi się bałagan, prawda?

użytkownik949300
źródło
wydaje się to ortogonalne w stosunku do pytania, robi się bałagan, czy są to metody statyczne, czy normalne
jk.
4
@ jk, chyba rozumiem jego punkt widzenia. Nie ma sensu mieć statycznej klasy kości, gdy myślisz o większej liczbie rodzajów kości. W takim przypadku możemy mieć różne obiekty kostki, modelując je za pomocą dobrych praktyk OOP.
Dherik
1
@TimothyTruckle Nie podważam tego, mówię tylko, że ta odpowiedź w rzeczywistości nie odpowiada na pytanie, ponieważ tego rodzaju pełzanie zakresu nie ma nic wspólnego z metodą statyczną, czy nie.
nvoigt
2
@nvoigt „Nie podważam tego” - no cóż. Najpotężniejszą cechą języka OO jest polimorfizm . I dostęp statyczna skutecznie uniemożliwia nam korzystanie z tego w ogóle. I masz rację: new Dice().roll(...)musi być concidered dostępu statycznego , jak również Dice.roll(...). Korzystamy tylko wtedy, gdy wstrzykniemy tę zależność.
Timothy Truckle,
2
@jk, z tą różnicą, że staticbałagan zdarza się przy każdym połączeniu, a potrzebna wiedza jest wymagana przy każdym połączeniu, więc globalnie rozpryskuje się po całej aplikacji. W strukturze OOP tylko konstruktor / fabryka musi być niechlujny, a szczegóły tej matrycy są zamknięte. Następnie użyj polimorfizmu i po prostu zadzwoń roll(). Mogę edytować tę odpowiedź, aby wyjaśnić.
user949300 11.0418
3

W szczególnym przypadku klasy kości myślę, że użycie metod instancji zamiast statyki znacznie ułatwi testowanie.

Jeśli chcesz przetestować na jednostce coś, co wykorzystuje instancję kości (np. Klasę gry), to z twoich testów możesz wstrzyknąć formę testu podwójnej kości, która zawsze zwraca ustaloną sekwencję wartości. Twój test może sprawdzić, czy gra ma odpowiedni wynik dla tych rzutów kostką.

Nie jestem programistą Java, ale myślę, że byłoby to znacznie trudniejsze w przypadku całkowicie statycznej klasy Dice. Zobacz /programming/4482315/why-does-mockito-not-mock-static-methods

bdsl
źródło
Tylko dla rekordów: w Javie mamy PowerMock, który zastępuje statyczne zależności poprzez manipulowanie kodem bajtowym. Ale korzystanie z niego jest po prostu poddaniem się złemu projektowi ...
Timothy Truckle,
0

Jest to tak naprawdę znane jako wzorzec Monostate , w którym każda instancja (a nawet „brak instancji”) ma ten sam status. Każdy członek jest członkiem klasy (tzn. Nie ma członków instancji). Są one powszechnie stosowane do implementowania klas „zestawu narzędzi”, które zawierają zestaw metod i stałych związanych z pojedynczą odpowiedzialnością lub wymaganiem, ale nie potrzebują stanu do działania (są one czysto funkcjonalne). W rzeczywistości Java jest dostarczana z niektórymi z nich w pakiecie (na przykład matematyka ).

Trochę nie na temat: rzadko zgadzam się z nazwami słów kluczowych w VisualBasic, ale w tym przypadku myślę, że sharedjest z pewnością jaśniejszy i semantycznie lepszy (jest dzielony między samą klasą i wszystkimi jej instancjami) niż static(pozostaje po cyklu życia zakresu, w którym jest zadeklarowany).

Jesus Alonso Abad
źródło