Ich habe festgestellt, dass fast jedes Mal, wenn ich Programmierer sehe, die statische Klassen in objektorientierten Sprachen wie C # verwenden, sie es falsch machen. Die Hauptprobleme sind offensichtlich der globale Zustand und die Schwierigkeit, Implementierungen zur Laufzeit oder mit Mocks / Stubs während der Tests auszutauschen.
Aus Neugier habe ich mir einige meiner Projekte angesehen und diejenigen ausgewählt, die gut getestet sind, und als ich mich bemühte, tatsächlich über Architektur und Design nachzudenken. Die zwei Verwendungen von statischen Klassen, die ich gefunden habe, sind:
Die Utility-Klasse - etwas, das ich lieber vermeiden würde, wenn ich diesen Code heute schreiben würde.
Der Anwendungscache: Die Verwendung einer statischen Klasse ist eindeutig falsch. Was ist, wenn ich es durch
MemoryCache
oder Redis ersetzen möchte ?
In .NET Framework sehe ich auch kein Beispiel für eine gültige Verwendung statischer Klassen. Zum Beispiel:
File
Die statische Klasse macht es wirklich schmerzhaft, zu Alternativen zu wechseln. Was ist beispielsweise, wenn Sie zu Isolated Storage wechseln oder Dateien direkt im Speicher speichern müssen oder einen Anbieter benötigen, der NTFS-Transaktionen unterstützt oder zumindest Pfade mit mehr als 259 Zeichen verarbeiten kann ? Nachdem eine gemeinsame Schnittstelle und mehrere Implementierungen erscheint so einfach wie mitFile
direkt, mit dem Vorteil nicht die meisten der Code - Basis , wenn Anforderungen ändern neu schreiben zu müssen.Console
Die statische Klasse macht das Testen zu kompliziert. Wie soll ich innerhalb eines Komponententests sicherstellen, dass eine Methode korrekte Daten an die Konsole ausgibt? Sollte ich die Methode ändern, um die Ausgabe an eine beschreibbare Datei zu senden, die zurStream
Laufzeit eingefügt wird? Es scheint, dass eine nicht statische Konsolenklasse wieder so einfach wie eine statische wäre, nur ohne alle Nachteile einer statischen Klasse.Math
Die statische Klasse ist auch kein guter Kandidat. Was ist, wenn ich auf Arithmetik mit beliebiger Genauigkeit umschalten muss? Oder zu einer neueren, schnelleren Implementierung? Oder zu einem Stub, der während eines Unit-Tests feststellt, dass eine bestimmte Methode einerMath
Klasse tatsächlich während eines Tests aufgerufen wurde?
Auf Programmer.SE habe ich gelesen:
Verwenden Sie nicht "statisch" in C #? Einige Antworten sind ziemlich gegen statische Klassen. Andere behaupten, dass „statische Methoden in Ordnung sind und einen rechtmäßigen Platz in der Programmierung haben.“, Backen sie jedoch nicht mit Argumenten.
Wann ein Singleton und wann eine statische Klasse verwendet werden soll . Dienstprogrammklassen werden erwähnt, da ich oben erwähnt habe, dass Dienstprogrammklassen problematisch sind.
Warum und wann sollte ich eine Klasse "statisch" machen? Was ist der Zweck des Schlüsselworts "statisch" für Klassen? Die einzige gültige Verwendung, die erwähnt wird, ist ein Container für Erweiterungsmethoden. Großartig.
Was sind neben den Erweiterungsmethoden die gültigen Verwendungszwecke statischer Klassen, dh Fälle, in denen Abhängigkeitsinjektion oder Singletons entweder unmöglich sind oder zu einem Design mit geringerer Qualität und einer schwierigeren Erweiterbarkeit und Wartung führen?
quelle
instanceof
), weil es in zwei FällenIFoo
keine Garantie gibt, dass sie von derselben Klasse unterstützt werden.YAGNI
, und akzeptieren , dass , wenn Sie jemals eine andere Art von String benötigen, Sie erhalten nur Ihre IDE jede Instanz zu ändern ,com.lang.lib.String
umcom.someones.elses.String
in der gesamten Codebasis.Antworten:
YAGNI
Ernsthaft. Wenn jemand verschiedene Implementierungen von
File
oderMath
oder verwenden möchte,Console
kann er den Schmerz durchmachen, dies weg zu abstrahieren. Hast du Java gesehen / benutzt?!? Die Java-Standardbibliotheken sind ein gutes Beispiel dafür, was passiert, wenn Sie Dinge zum Zwecke der Abstraktion abstrahieren. Muss ich wirklich drei Schritte ausführen, um mein Kalenderobjekt zu erstellen ?Alles in allem werde ich nicht hier sitzen und statische Klassen stark verteidigen. Der statische Zustand ist absolut böse (auch wenn ich ihn gelegentlich immer noch verwende, um Dinge zu bündeln / zwischenzuspeichern). Statische Funktionen können aufgrund von Problemen mit Parallelität und Testbarkeit böse sein.
Aber reine statische Funktionen sind nicht so schrecklich. Es gibt elementare Dinge, deren Abstraktion keinen Sinn macht, da es keine vernünftige Alternative für die Operation gibt. Es gibt Implementierungen einer Strategie, die statisch sein können, da sie bereits über den Delegaten / Funktor abstrahiert wurden. Und manchmal haben diese Funktionen keine Instanzklasse, der sie zugeordnet werden können. Daher sind statische Klassen in Ordnung, um sie zu organisieren.
Erweiterungsmethoden sind auch eine praktische Anwendung, auch wenn sie keine philosophisch statischen Klassen sind.
quelle
Console
sollte eigentlich nicht Teil des systemweiten Zustands sein, aber auch nicht Teil eines Objekts, das ständig herumgereicht wird. Stattdessen scheint es sinnvoller zu sein zu sagen, dass jeder Thread eine "aktuelle Konsole" -Eigenschaft hat, die einfach gespeichert und wiederhergestellt werden kann, wenn eine Routine erforderlich ist, die schreibt,Console
um stattdessen etwas anderes zu verwenden.Mit einem Wort Einfachheit.
Wenn Sie zu viel entkoppeln, erhalten Sie die Hallo-Welt aus der Hölle.
Aber hey, zumindest gibt es keine XML-Konfiguration, was eine nette Geste ist.
Die statische Klasse ermöglicht die Optimierung für den schnellen und allgemeinen Fall. Der meiste Punkt, den Sie erwähnt haben, kann sehr einfach gelöst werden, indem Sie Ihre eigene Abstraktion darüber einführen oder Dinge wie Console.Out verwenden.
quelle
Der Fall für statische Methoden: Dies:
ist viel einfacher und klarer und lesbarer als dies:
Fügen Sie nun die Abhängigkeitsinjektion hinzu, um die expliziten Instanziierungen auszublenden, und sehen Sie, wie der
Max
Einzeiler in zehn oder Hunderte von Zeilen wächst - alles, um das unwahrscheinliche Szenario zu unterstützen, dass Sie Ihre eigene Implementierung erfinden, die erheblich schneller ist als die im Framework und die Sie benötigen transparent zwischen den alternativenMax
Implementierungen wechseln zu können.API-Design ist ein Kompromiss zwischen Einfachheit und Flexibilität. Sie können jederzeit zusätzliche Indirektionsebenen ( ad infinitum ) einführen , müssen jedoch die Bequemlichkeit des allgemeinen Falls gegen den Vorteil zusätzlicher Ebenen abwägen.
Beispielsweise können Sie jederzeit einen eigenen Instanz-Wrapper um die Dateisystem-API schreiben, wenn Sie in der Lage sein müssen, transparent zwischen Speicheranbietern zu wechseln. Ohne die einfacheren statischen Methoden wäre jeder gezwungen, jedes Mal eine Instanz zu erstellen, wenn er eine einfache Aufgabe wie das Speichern einer Datei ausführen müsste. Es wäre wirklich ein schlechter Design-Kompromiss, für einen äußerst seltenen Randfall zu optimieren und alles für den allgemeinen Fall komplizierter zu machen. Das Gleiche gilt für
Console
- Sie können ganz einfach Ihren eigenen Wrapper erstellen, wenn Sie die Konsole abstrahieren oder verspotten müssen, aber häufig verwenden Sie die Konsole für schnelle Lösungen oder Debugging-Zwecke, sodass Sie nur die einfachste Möglichkeit zur Ausgabe von Text wünschen.Während "eine gemeinsame Schnittstelle mit mehreren Implementierungen" cool ist, gibt es nicht unbedingt eine "richtige" Abstraktion, die alle gewünschten Speicheranbieter vereint. Möglicherweise möchten Sie zwischen Dateien und isoliertem Speicher wechseln, aber eine andere Person möchte möglicherweise zwischen Dateien, Datenbank und Cookies wechseln, um sie zu speichern. Die gemeinsame Oberfläche würde anders aussehen, und es ist unmöglich, alle möglichen Abstraktionen vorherzusagen, die Benutzer wünschen könnten. Es ist viel flexibler, statische Methoden als "Grundelemente" bereitzustellen und dann Benutzer ihre eigenen Abstraktionen und Wrapper erstellen zu lassen.
Ihr
Math
Beispiel ist noch zweifelhafter. Wenn Sie zu Mathematik mit beliebiger Genauigkeit wechseln möchten, benötigen Sie vermutlich neue numerische Typen. Dies wäre eine grundlegende Änderung der Du ganzen Programms, und ändern zu müssen ,Math.Max()
umArbitraryPrecisionMath.Max()
ist sicherlich die am wenigsten von euch Sorgen.Das heißt, ich stimme zu, dass Stateful statische Klassen sind in den meisten Fällen das Böse.
quelle
Im Allgemeinen sind die einzigen Orte, an denen ich statische Klassen verwenden würde, diejenigen, an denen die Klasse reine Funktionen hostet, die keine Systemgrenzen überschreiten. Mir ist klar, dass Letzteres überflüssig ist, da alles, was eine Grenze überschreitet, wahrscheinlich absichtlich nicht rein ist. Ich komme zu diesem Schluss sowohl aus einem Misstrauen gegenüber dem globalen Staat als auch aus einer leicht zu testenden Perspektive. Selbst wenn die begründete Erwartung besteht, dass es eine einzige Implementierung für eine Klasse gibt, die eine Systemgrenze überschreitet (Netzwerk, Dateisystem, E / A-Gerät), verwende ich eher Instanzen als statische Klassen, da ein Mock bereitgestellt werden kann Die gefälschte Implementierung in Komponententests ist entscheidend für die Entwicklung schneller Komponententests ohne Kopplung an tatsächliche Geräte. In Fällen, in denen die Methode eine reine Funktion ohne Kopplung an ein externes System / Gerät ist, kann eine statische Klasse meiner Meinung nach angemessen sein, aber nur, wenn sie die Testbarkeit nicht beeinträchtigt. Ich finde die Anwendungsfälle außerhalb bestehender Framework-Klassen relativ selten. Darüber hinaus kann es Fälle geben, in denen Sie keine andere Wahl haben - beispielsweise Erweiterungsmethoden in C #.
quelle