Ich schaue mir einige Open-Source-Java-Projekte an, um in Java einzusteigen, und stelle fest, dass viele von ihnen eine Art 'Konstanten'-Schnittstelle haben.
Zum Beispiel processing.org verfügt über eine Schnittstelle namens PConstants.java , und die meisten anderen Hauptklassen implementieren diese Schnittstelle. Die Schnittstelle ist mit statischen Elementen durchsetzt. Gibt es einen Grund für diesen Ansatz oder wird dies als schlechte Praxis angesehen? Warum nicht Enums verwenden, wo es Sinn macht , oder eine statische Klasse?
Ich finde es seltsam, eine Schnittstelle zu verwenden, um eine Art pseudo-globaler Variablen zuzulassen.
public interface PConstants {
// LOTS OF static fields...
static public final int SHINE = 31;
// emissive (by default kept black)
static public final int ER = 32;
static public final int EG = 33;
static public final int EB = 34;
// has this vertex been lit yet
static public final int BEEN_LIT = 35;
static public final int VERTEX_FIELD_COUNT = 36;
// renderers known to processing.core
static final String P2D = "processing.core.PGraphics2D";
static final String P3D = "processing.core.PGraphics3D";
static final String JAVA2D = "processing.core.PGraphicsJava2D";
static final String OPENGL = "processing.opengl.PGraphicsOpenGL";
static final String PDF = "processing.pdf.PGraphicsPDF";
static final String DXF = "processing.dxf.RawDXF";
// platform IDs for PApplet.platform
static final int OTHER = 0;
static final int WINDOWS = 1;
static final int MACOSX = 2;
static final int LINUX = 3;
static final String[] platformNames = {
"other", "windows", "macosx", "linux"
};
// and on and on
}
static final
ist nicht erforderlich, es ist für eine Schnittstelle redundant.platformNames
sein kannpublic
,static
undfinal
, aber es ist definitiv nicht eine Konstante. Das einzige konstante Array ist eins mit der Länge Null.static final
ist nicht unbedingt redundant. Ein Klassen- oder Schnittstellenfeld mit nur demfinal
Schlüsselwort würde separate Instanzen dieses Felds erstellen, wenn Sie Objekte der Klasse oder Schnittstelle erstellen. Durchstatic final
die Verwendung würde jedes Objekt einen Speicherort für dieses Feld gemeinsam nutzen. Mit anderen Worten, wenn eine Klasse MyClass ein Feldfinal String str = "Hello";
hätte, wären für N Instanzen von MyClass N Instanzen des Feldes str im Speicher. Das Hinzufügen desstatic
Schlüsselworts würde nur zu einer Instanz führen.Antworten:
Es wird allgemein als schlechte Praxis angesehen. Das Problem ist, dass die Konstanten Teil der öffentlichen "Schnittstelle" (mangels eines besseren Wortes) der implementierenden Klasse sind. Dies bedeutet, dass die implementierende Klasse alle diese Werte für externe Klassen veröffentlicht, auch wenn sie nur intern benötigt werden. Die Konstanten vermehren sich im gesamten Code. Ein Beispiel ist die SwingConstants- Schnittstelle in Swing, die von Dutzenden von Klassen implementiert wird, die alle ihre Konstanten (auch diejenigen, die sie nicht verwenden) als ihre eigenen " reexportieren " .
Aber nimm nicht nur mein Wort dafür, Josh Bloch sagt auch, dass es schlecht ist:
Eine Aufzählung kann ein besserer Ansatz sein. Oder Sie können die Konstanten einfach als öffentliche statische Felder in eine Klasse einfügen, die nicht instanziiert werden kann. Dadurch kann eine andere Klasse auf sie zugreifen, ohne ihre eigene API zu verschmutzen.
quelle
Anstatt eine "Konstantenschnittstelle" zu implementieren, können Sie in Java 1.5+ statische Importe verwenden, um die Konstanten / statischen Methoden aus einer anderen Klasse / Schnittstelle zu importieren:
Dies vermeidet die Hässlichkeit, Ihre Klassen dazu zu bringen, Schnittstellen zu implementieren, die keine Funktionalität haben.
Ich denke, dass es manchmal notwendig ist, eine Klasse nur zum Speichern von Konstanten zu haben. Es gibt bestimmte Konstanten, die einfach keinen natürlichen Platz in einer Klasse haben, daher ist es besser, sie an einem "neutralen" Ort zu haben.
Verwenden Sie jedoch anstelle einer Schnittstelle eine letzte Klasse mit einem privaten Konstruktor. (Dies macht es unmöglich, die Klasse zu instanziieren oder zu unterklassifizieren, und sendet eine starke Nachricht, dass sie keine nicht statischen Funktionen / Daten enthält.)
Z.B:
quelle
Ich gebe nicht vor, richtig zu sein, aber sehen wir uns dieses kleine Beispiel an:
ToyotaCar weiß nichts über FordCar und FordCar weiß nichts über ToyotaCar. Prinzip CarConstants sollten geändert werden, aber ...
Konstanten sollten nicht geändert werden, da das Rad rund und der Motor mechanisch ist, aber ... In Zukunft haben Toyotas Forschungsingenieure den elektronischen Motor und die platten Räder erfunden! Sehen wir uns unsere neue Oberfläche an
und jetzt können wir unsere Abstraktion ändern:
zu
Und jetzt, wenn wir jemals den Kernwert von ENGINE oder WHEEL ändern müssen, können wir die ToyotaCar-Schnittstelle auf Abstraktionsebene ändern, ohne Implementierungen zu berühren
Es ist NICHT SICHER, ich weiß, aber ich möchte immer noch wissen, dass Sie darüber nachdenken
quelle
Es gibt viel Hass für dieses Muster in Java. Eine Schnittstelle mit statischen Konstanten hat jedoch manchmal einen Wert. Grundsätzlich müssen Sie folgende Bedingungen erfüllen:
Die Konzepte sind Teil der öffentlichen Schnittstelle mehrerer Klassen.
Ihre Werte können sich in zukünftigen Versionen ändern.
Angenommen, Sie schreiben eine Erweiterung für eine hypothetische Abfragesprache. In dieser Erweiterung erweitern Sie die Sprachsyntax um einige neue Operationen, die von einem Index unterstützt werden. Beispiel: Sie werden einen R-Tree haben, der Geodatenabfragen unterstützt.
Sie schreiben also eine öffentliche Schnittstelle mit der statischen Konstante:
Jetzt später glaubt ein neuer Entwickler, er müsse einen besseren Index erstellen, also kommt er und erstellt eine R * -Implementierung. Durch die Implementierung dieser Schnittstelle in seinem neuen Baum garantiert er, dass die verschiedenen Indizes in der Abfragesprache dieselbe Syntax haben. Wenn Sie später entschieden haben, dass "nearTo" ein verwirrender Name ist, können Sie ihn in "insideDistanceInKm" ändern und wissen, dass die neue Syntax von allen Ihren Indeximplementierungen respektiert wird.
PS: Die Inspiration für dieses Beispiel stammt aus dem Neo4j-Raumcode.
quelle
Im Nachhinein können wir sehen, dass Java in vielerlei Hinsicht kaputt ist. Ein Hauptfehler von Java ist die Beschränkung der Schnittstellen auf abstrakte Methoden und statische Endfelder. Neuere, komplexere OO-Sprachen wie Scala fassen Schnittstellen nach Merkmalen zusammen, die konkrete Methoden enthalten können (und dies normalerweise auch tun), die Arität Null haben können (Konstanten!). Eine Darstellung von Merkmalen als Einheiten zusammensetzbaren Verhaltens finden Sie unter http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf . Eine kurze Beschreibung des Vergleichs von Merkmalen in Scala mit Schnittstellen in Java finden Sie unter http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. Im Zusammenhang mit dem Unterrichten von OO-Design sind vereinfachende Regeln wie die Behauptung, dass Schnittstellen niemals statische Felder enthalten sollten, dumm. Viele Merkmale enthalten natürlich Konstanten, und diese Konstanten sind angemessenerweise Teil der öffentlichen "Schnittstelle", die durch das Merkmal unterstützt wird. Beim Schreiben von Java-Code gibt es keine saubere und elegante Möglichkeit, Merkmale darzustellen. Die Verwendung statischer Endfelder innerhalb von Schnittstellen ist jedoch häufig Teil einer guten Problemumgehung.
quelle
Gemäß der JVM-Spezifikation können Felder und Methoden in einer Schnittstelle nur öffentlich, statisch, endgültig und abstrakt sein. Ref aus Inside Java VM
Standardmäßig sind alle Methoden in der Schnittstelle abstrakt, auch wenn Sie sie nicht explizit erwähnt haben.
Schnittstellen sollen nur Angaben machen. Es kann keine Implementierungen enthalten. Um zu vermeiden, dass Klassen implementiert werden, um die Spezifikation zu ändern, wird sie endgültig festgelegt. Da die Schnittstelle nicht instanziiert werden kann, werden sie statisch gemacht, um über den Schnittstellennamen auf das Feld zuzugreifen.
quelle
Ich habe nicht genug Ruf, um Pleerock einen Kommentar zu geben, deshalb muss ich eine Antwort erstellen. Das tut mir leid, aber er hat sich sehr bemüht und ich würde ihm gerne antworten.
Pleerock, Sie haben das perfekte Beispiel erstellt, um zu zeigen, warum diese Konstanten unabhängig von Schnittstellen und unabhängig von Vererbung sein sollten. Für den Kunden der Anwendung ist es nicht wichtig, dass es einen technischen Unterschied zwischen der Implementierung von Autos gibt. Sie sind für den Kunden gleich, nur für Autos. Der Client möchte sie also aus dieser Perspektive betrachten, die eine Schnittstelle wie I_Somecar darstellt. Während der gesamten Anwendung verwendet der Kunde nur eine Perspektive und nicht unterschiedliche für jede unterschiedliche Automarke.
Wenn ein Kunde Autos vor dem Kauf vergleichen möchte, kann er eine Methode wie die folgende anwenden:
Eine Schnittstelle ist ein Verhaltensvertrag und zeigt verschiedene Objekte aus einer Perspektive. So wie Sie es gestalten, hat jede Automarke ihre eigene Vererbungslinie. Obwohl es in Wirklichkeit ganz richtig ist, weil Autos so unterschiedlich sein können, dass es wie der Vergleich völlig unterschiedlicher Objekttypen sein kann, gibt es am Ende die Wahl zwischen verschiedenen Autos. Und das ist die Perspektive der Schnittstelle, die alle Marken teilen müssen. Die Wahl der Konstanten sollte dies nicht unmöglich machen. Bitte beachten Sie die Antwort von Zarkonnen.
quelle
Dies kam aus einer Zeit, bevor Java 1.5 existiert und uns Aufzählungen bringt. Zuvor gab es keine gute Möglichkeit, eine Reihe von Konstanten oder eingeschränkten Werten zu definieren.
Dies wird immer noch verwendet, meistens entweder aus Gründen der Abwärtskompatibilität oder aufgrund der Menge an Refactoring, die erforderlich ist, um in vielen Projekten entfernt zu werden.
quelle