Ich habe die Diskussion bei dieser Frage gesehen, wie eine Klasse, die von einer Schnittstelle implementiert wird, instanziiert wird. In meinem Fall schreibe ich ein sehr kleines Programm in Java, das eine Instanz von verwendet TreeMap
, und nach Meinung aller sollte es wie folgt instanziiert werden:
Map<X> map = new TreeMap<X>();
In meinem Programm rufe ich die Funktion auf map.pollFirstEntry()
, die nicht in der Map
Schnittstelle deklariert ist (und einige andere, die auch in der Map
Schnittstelle vorhanden sind). Ich habe es geschafft, indem TreeMap<X>
ich auf eine Stelle gewirkt habe, die ich diese Methode nenne:
someEntry = ((TreeMap<X>) map).pollFirstEntry();
Ich verstehe die Vorteile der oben beschriebenen Initialisierungsrichtlinien für große Programme, aber für ein sehr kleines Programm, bei dem dieses Objekt nicht an andere Methoden übergeben wird, halte ich es für unnötig. Trotzdem schreibe ich diesen Beispielcode als Teil einer Bewerbung und möchte nicht, dass mein Code schlecht oder überladen aussieht. Was wäre die eleganteste Lösung?
EDIT: Ich möchte darauf hinweisen, dass ich mehr an den allgemeinen guten Codierungspraktiken interessiert bin als an der Anwendung der spezifischen Funktion TreeMap
. Wie einige der Antworten bereits gezeigt haben (und ich habe den ersten als beantwortet markiert), sollte die mögliche höhere Abstraktionsebene verwendet werden, ohne die Funktionalität zu verlieren.
quelle
Antworten:
"Programmieren auf eine Schnittstelle" bedeutet nicht "Verwenden Sie die am besten abstrahierte Version". In diesem Fall würde jeder nur verwenden
Object
.Dies bedeutet, dass Sie Ihr Programm anhand der niedrigstmöglichen Abstraktion definieren sollten, ohne die Funktionalität zu verlieren . Wenn Sie a benötigen, müssen
TreeMap
Sie einen Vertrag mit a definierenTreeMap
.quelle
TreeMap
als Beispiel verwendet. "Programm auf eine Schnittstelle" sollte nicht wörtlich als Schnittstelle oder abstrakte Klasse verstanden werden - eine Implementierung kann auch als Schnittstelle betrachtet werden.public interface
eher auf eine als auf 'die öffentlichen Methoden einer Implementierung' programmieren möchten. .TreeMap
gegenüberSortedMap
oderNavigableMap
Wenn Sie weiterhin eine Schnittstelle verwenden möchten, können Sie diese verwenden
Es ist nicht erforderlich, immer eine Schnittstelle zu verwenden, aber es gibt häufig einen Punkt, an dem Sie eine allgemeinere Ansicht einnehmen möchten, mit der Sie die Implementierung ersetzen können (möglicherweise zum Testen). Dies ist einfach, wenn alle Verweise auf das Objekt als das abstrahiert werden Oberflächentyp.
quelle
Der Grund für die Codierung in eine Schnittstelle und nicht in eine Implementierung besteht darin, zu vermeiden, dass Implementierungsdetails verloren gehen, die Ihr Programm sonst einschränken würden.
Betrachten Sie die Situation, in der die Originalversion Ihres Codes a verwendet
HashMap
und diese offengelegt hat.Dies bedeutet, dass jede Änderung an
getFoo()
eine brechende API-Änderung darstellt und die Benutzer unglücklich machen würde. Wenn Sie nur garantieren, dassfoo
es sich um eine Karte handelt, sollten Sie diese stattdessen zurückgeben.Dies gibt Ihnen die Flexibilität, die Funktionsweise Ihres Codes zu ändern. Sie erkennen, dass es sich
foo
tatsächlich um eine Karte handeln muss, die die Dinge in einer bestimmten Reihenfolge zurückgibt.Und das bricht nichts für den Rest des Codes.
Sie können den Vertrag, den die Klasse gibt, später stärken, ohne etwas zu brechen.
Dies befasst sich mit dem Liskov-Substitutionsprinzip
Da NavigableMap ein Untertyp von Map ist, kann diese Ersetzung ohne Änderung des Programms durchgeführt werden.
Das Offenlegen der Implementierungstypen macht es schwierig, die interne Funktionsweise des Programms zu ändern, wenn eine Änderung vorgenommen werden muss. Dies ist ein schmerzhafter Prozess und führt oft zu hässlichen Problemumgehungen, die dem Codierer später nur noch mehr Schmerzen zufügen (ich sehe Ihren vorherigen Codierer, der aus irgendeinem Grund ständig Daten zwischen einer LinkedHashMap und einer TreeMap gemischt hat - vertrauen Sie mir, wann immer ich es tue siehe deinen Namen in svn Schuld, ich mache mir Sorgen).
Sie möchten weiterhin vermeiden, dass Implementierungstypen verloren gehen. Beispielsweise möchten Sie möglicherweise ConcurrentSkipListMap stattdessen aufgrund einiger Leistungsmerkmale implementieren, oder Sie mögen nur die
java.util.concurrent.ConcurrentSkipListMap
und nichtjava.util.TreeMap
die Importanweisungen oder was auch immer.quelle
Stimmen Sie den anderen Antworten zu, dass Sie die allgemeinste Klasse (oder Schnittstelle) verwenden sollten, von der Sie tatsächlich etwas benötigen, in diesem Fall TreeMap (oder wie jemand NavigableMap vorgeschlagen hat). Aber ich möchte hinzufügen, dass dies mit Sicherheit besser ist, als überall darauf zu werfen, was auf jeden Fall ein viel größerer Geruch wäre. Siehe /programming/4167304/why-should-casting-be-avoided aus bestimmten Gründen.
quelle
Es geht um Ihre Kommunikation Absicht der , wie sollte das Objekt verwendet werden. Wenn Ihre Methode beispielsweise ein
Map
Objekt mit einer vorhersagbaren Iterationsreihenfolge erwartet :Wenn Sie den Aufrufern der oben genannten Methode unbedingt mitteilen müssen, dass auch sie ein
Map
Objekt mit einer vorhersagbaren Iterationsreihenfolge zurückgeben , da aus irgendeinem Grund eine solche Erwartung besteht:Natürlich können Aufrufer das Rückgabeobjekt weiterhin
Map
als solches behandeln, aber das geht über den Rahmen Ihrer Methode hinaus:Einen Schritt zurück machen
Der allgemeine Ratschlag zum Codieren einer Schnittstelle ist (allgemein) anwendbar, da normalerweise die Schnittstelle die Garantie dafür bietet, was das Objekt ausführen kann, auch bekannt als Vertrag . Viele Anfänger beginnen mit
HashMap<K, V> map = new HashMap<>()
und es wird empfohlen, dies als a zu deklarierenMap
, da aHashMap
nicht mehr bietet als das, was aMap
tun soll. Daraus können sie dann (hoffentlich) verstehen, warum ihre Methoden aMap
anstelle von a aufnehmen solltenHashMap
, und so können sie die Vererbungsfunktion in OOP realisieren.Um nur eine Zeile aus dem Wikipedia-Eintrag des Lieblingsprinzips aller zu diesem Thema zu zitieren :
Mit anderen Worten, die Verwendung einer
Map
Deklaration liegt nicht daran, dass sie "syntaktisch" sinnvoll ist, sondern Aufrufe des Objekts sollten sich nur darum kümmern, dass es sich um eine Art von Deklaration handeltMap
.Sauberer Code
Ich finde, dass ich dadurch manchmal auch saubereren Code schreiben kann, insbesondere wenn es um Unit-Tests geht. Das Erstellen eines
HashMap
mit einem einzigen schreibgeschützten Testeintrags dauert mehr als eine Zeile (ohne die Verwendung der doppelten Klammerinitialisierung), wenn ich dies leicht durch ersetzen kannCollections.singletonMap()
.quelle