Java - Ist es eine schlechte Idee, vollständig statische Klassen zu haben?

16

Ich arbeite gerade an einem größeren Soloprojekt und habe mehrere Klassen, in denen ich keinen Grund sehe, eine Instanz von zu erstellen.

Meine aktuelle Würfelklasse speichert beispielsweise alle ihre Daten statisch und alle ihre Methoden sind auch statisch. Ich muss es nicht initialisieren, denn wenn ich würfeln und einen neuen Wert erhalten möchte, benutze ich einfach Dice.roll().

Ich habe mehrere ähnliche Klassen, die nur eine Hauptfunktion wie diese haben, und ich beginne mit der Arbeit an einer Art "Controller" -Klasse, die für alle Ereignisse zuständig ist (z. B. wann sich ein Spieler bewegt und welcher Zug gerade ist) es ist) und ich habe festgestellt, dass ich die gleiche Idee für diese Klasse folgen könnte. Ich habe nie vor, mehrere Objekte für diese spezifischen Klassen zu erstellen. Wäre es also eine schlechte Idee, sie vollständig statisch zu machen?

Ich habe mich gefragt, ob dies in Bezug auf Java als "schlechte Praxis" angesehen wird. Nach allem, was ich gesehen habe, scheint die Community in Bezug auf dieses Thema gespalten zu sein. Jedenfalls würde ich gerne darüber diskutieren und Links zu Ressourcen wären auch großartig!

HexTeke
quelle
1
Wenn Ihr Programm vollständig prozedural ist. Warum hast du dich für Java entschieden?
Laiv
12
Versuchen Sie, Komponententests für diese Klassen zu schreiben, und Sie werden herausfinden, warum die Leute keine statischen Methoden mögen, die auf den statischen Status zugreifen.
Joeri Sebrechts
@Laiv Ich bin noch ein bisschen neu in der Programmierung, ungefähr ein Jahr in C ++, ich nehme in diesem Semester an einem Java-Kurs teil und ich fange an, Java viel mehr zu mögen, besonders die Grafikbibliotheken.
HexTeke
5
In Bezug auf statische Klassen. Wenn statische Methoden rein sind (halten Sie keinen Zustand, haben Sie Eingabeargumente und einen Rückgabetyp). Es gibt nichts zu
befürchten

Antworten:

20

An statischen Klassen, die wirklich statisch sind, ist nichts auszusetzen . Das heißt, es gibt keinen internen Zustand, von dem die Ausgabe der Methoden sich ändern würde.

Wenn Dice.roll()einfach eine neue Zufallszahl von 1 bis 6 zurückgegeben wird, ändert sich der Status nicht. Zugegeben, Sie teilen möglicherweise eine RandomInstanz, aber ich würde nicht in Betracht ziehen, dass bei einer Statusänderung per Definition die Ausgabe immer gut und zufällig sein wird. Es ist auch threadsicher, so dass es hier keine Probleme gibt.

Es werden häufig endgültige "Helper" - oder andere Dienstprogrammklassen angezeigt, die einen privaten Konstruktor und statische Member haben. Der private Konstruktor enthält keine Logik und dient nur dazu, zu verhindern, dass jemand die Klasse instanziiert. Der letzte Modifikator bringt nur die Idee auf den Punkt, dass dies keine Klasse ist, von der Sie jemals abgeleitet werden möchten. Es ist nur eine Gebrauchsklasse. Bei ordnungsgemäßer Ausführung sollten keine Singleton- oder anderen Klassenmitglieder vorhanden sein, die selbst nicht statisch und endgültig sind.

Solange Sie diesen Richtlinien folgen und keine Singletons machen, ist daran absolut nichts auszusetzen. Sie erwähnen eine Controller-Klasse, und dies erfordert mit ziemlicher Sicherheit Statusänderungen. Ich rate daher davon ab, nur statische Methoden zu verwenden. Sie können sich stark auf eine statische Dienstprogrammklasse verlassen, sie jedoch nicht zu einer statischen Dienstprogrammklasse machen.


Was ist eine Zustandsänderung für eine Klasse? Lassen Sie uns Zufallszahlen für eine Sekunde ausschließen, da sie per Definition nicht deterministisch sind und sich daher der Rückgabewert häufig ändert.

Eine reine Funktion ist eine deterministische, dh für eine bestimmte Eingabe erhalten Sie genau eine Ausgabe. Sie möchten, dass statische Methoden reine Funktionen sind. In Java gibt es Möglichkeiten, das Verhalten statischer Methoden zu optimieren, um den Status zu halten, aber es sind fast nie gute Ideen. Wenn Sie eine Methode als statisch deklarieren , geht der typische Programmierer von vornherein davon aus, dass es sich um eine reine Funktion handelt. Abweichend vom erwarteten Verhalten neigen Sie generell dazu, Fehler in Ihrem Programm zu verursachen und sollten vermieden werden.

Ein Singleton ist eine Klasse, die statische Methoden enthält, die der "reinen Funktion" so entgegengesetzt sind, wie Sie nur können. Ein einzelnes statisches privates Mitglied wird intern in der Klasse gespeichert, um sicherzustellen, dass genau eine Instanz vorhanden ist. Dies ist keine bewährte Methode und kann Sie aus verschiedenen Gründen später in Schwierigkeiten bringen. Um zu wissen, wovon wir sprechen, hier ein einfaches Beispiel für einen Singleton:

// 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
quelle
7
Wenn ich testen möchte, was passiert, wenn eine Doppelsechs gewürfelt wird, stecke ich hier fest, da Sie eine statische Klasse mit einem statischen Zufallszahlengenerator haben. Nein, Dice.roll()ist keine gültige Ausnahme von der Regel "kein globaler Status".
David Arno
1
@HexTeke Aktualisierte Antwort.
Neil
1
@DavidArno Stimmt, aber ich nehme an, dass wir an diesem Punkt wirklich in Schwierigkeiten sind, wenn wir einen einzelnen Anruf an testen random.nextInt(6) + 1. ;)
Neil
3
@Neil, entschuldigung, ich habe mich nicht sehr gut erklärt. Wir testen die Zufallszahlenfolge nicht, sondern beeinflussen sie, um andere Tests zu unterstützen. Wenn wir testen, dass z. B. RollAndMove()erneut gewürfelt wird, wenn wir eine Doppelsechs liefern, dann ist die einfachste und robusteste Möglichkeit, dies zu tun, entweder Diceden Zufallszahlengenerator auszuspielen. Ergo, Dicemöchte keine statische Klasse sein, die einen statischen Zufallsgenerator verwendet.
David Arno
12
Ihr Update trifft das Problem jedoch auf den Kopf: Statische Methoden sollten deterministisch sein. Sie sollten keine Nebenwirkungen haben.
David Arno
9

Um ein Beispiel für die Einschränkungen einer staticKlasse zu geben, was ist, wenn einige Ihrer Spieler einen leichten Bonus auf ihre Würfelwürfe erhalten möchten? Und sie sind bereit, viel Geld zu bezahlen! :-)

Ja, Sie können einen weiteren Parameter hinzufügen Dice.roll(bonus).

Später brauchst du D20s.

Dice.roll(bonus, sides)

Ja, aber einige Spieler haben die "überragende Leistung", so dass sie niemals "fummeln" können (1 würfeln).

Dice.roll(bonus, sides, isFumbleAllowed).

Das wird unordentlich, nicht wahr?

user949300
quelle
dies scheint orthogonal zu der Frage zu sein, ob es sich um statische Methoden oder um normale Methoden handelt
jk.
4
@jk, ich glaube, ich habe seinen Punkt verstanden. Es macht keinen Sinn, eine statische Klasse für Würfel zu haben, wenn Sie an mehr Arten von Würfeln denken. In diesem Fall können wir verschiedene Würfelobjekte haben, die mit guten OOP-Methoden modelliert werden.
Dherik
1
@ TimothyTruckle Ich bestreite das nicht, alles was ich sage ist, dass diese Antwort die Frage nicht wirklich beantwortet, weil diese Art von Scope Creep nichts damit zu tun hat, dass die Methode statisch ist oder nicht.
Nvoigt
2
@nvoigt „Ich streite nicht , dass“ - Nun, ich tue. Das mächtigste Merkmal einer OO-Sprache ist der Polymorphismus . Und statischer Zugriff verhindert effektiv, dass wir das überhaupt nutzen können. Und Sie haben Recht: new Dice().roll(...)Muss ebenso wie statischer Zugriff berücksichtigt werden Dice.roll(...). Wir profitieren nur, wenn wir diese Abhängigkeit injizieren .
Timothy Truckle
2
@jk Der Unterschied besteht darin, dass staticbei jedem Anruf Unordnung auftritt und dass bei jedem Anruf das erforderliche Wissen erforderlich ist, damit die gesamte Anwendung global mit Unordnung überschwemmt wird . In einer OOP-Struktur muss nur der Konstruktor / die Factory chaotisch sein, und die Details dieses Chips sind gekapselt. Verwenden Sie danach Polymorphismus und rufen Sie einfach an roll(). Ich könnte diese Antwort zur Klarstellung bearbeiten.
user949300
3

Im speziellen Fall einer Dice-Klasse wird das Testen meiner Meinung nach durch die Verwendung von Instanzmethoden anstelle von statischen Methoden erheblich vereinfacht.

Wenn Sie einen Komponententest durchführen möchten, bei dem eine Instanz von Dice verwendet wird (z. B. eine Spielklasse), können Sie aus Ihren Tests eine Art Test-Double von Dice injizieren, das immer eine feste Folge von Werten zurückgibt. Ihr Test kann überprüfen, ob das Spiel das richtige Ergebnis für diese Würfelwürfe hat.

Ich bin kein Java-Entwickler, aber ich denke, es wäre bedeutend schwieriger, dies mit einer vollständig statischen Dice-Klasse zu tun. Siehe /programming/4482315/why-does-mockito-not-mock-static-methods

bdsl
quelle
Nur für die Datensätze: In Java haben wir PowerMock , um statische Abhängigkeiten durch Manipulation des Bytecodes zu ersetzen. Aber es zu benutzen ist nur eine Kapitulation vor schlechtem Design ...
Timothy Truckle
0

Dies ist eigentlich als das Monostate-Muster bekannt , bei dem jede Instanz (und sogar eine "Nicht-Instanz") ihren Status teilt. Jedes Mitglied ist ein Klassenmitglied (dh kein Instanzmitglied). Sie werden häufig verwendet, um "Toolkit" -Klassen zu implementieren, die eine Reihe von Methoden und Konstanten bündeln, die sich auf eine einzelne Verantwortung oder Anforderung beziehen, aber keinen Status benötigen, um zu funktionieren (sie sind rein funktional). In der Tat wird Java mit einigen davon gebündelt geliefert (zum Beispiel Math ).

Ein bisschen off-topic: ich mit der Benennung von Schlüsselwort in VisualBasic- selten zustimmen, aber in diesem Fall, ich denke , sharedist sicherlich klarer und besser semantisch (es wird geteilt als in der Klasse selbst und alle seine Instanzen) static(bleibt nach dem Lebenszyklus des Geltungsbereichs, in dem es deklariert ist).

Jesus Alonso Abad
quelle