Ist "Parent x = new Child ();" anstelle von "Child x = new Child ();" eine schlechte Praxis, wenn wir die letztere verwenden können?

32

Zum Beispiel hatte ich einige Codes gesehen, die ein Fragment wie dieses erzeugen:

Fragment myFragment=new MyFragment();

Dies deklariert eine Variable als Fragment anstelle von MyFragment. MyFragment ist eine untergeordnete Klasse von Fragment. Ich bin mit dieser Codezeile nicht zufrieden, da ich der Meinung bin, dass dieser Code wie folgt lauten sollte:

MyFragment myFragment=new MyFragment();

Was ist genauer, ist das wahr?

Oder ist es bei der Verallgemeinerung der Frage eine schlechte Praxis, Folgendes zu verwenden:

Parent x=new Child();

anstatt

Child x=new Child();

können wir das erstere in das letztere umwandeln, ohne kompilierungsfehler?

ggrr
quelle
6
Wenn wir Letzteres tun, hat Abstraktion / Verallgemeinerung keinen Sinn mehr. Natürlich gibt es Ausnahmen ...
Walfrat
2
In einem Projekt, an dem ich arbeite, erwarten meine Funktionen den übergeordneten Typ und führen übergeordnete Typmethoden aus. Ich kann eines von mehreren Kindern akzeptieren, und der Code hinter diesen (geerbten und überschriebenen) Methoden ist unterschiedlich, aber ich möchte diesen Code ausführen, unabhängig davon, welches Kind ich verwende.
Stephen S
4
@Walfrat In der Tat macht Abstraktion wenig Sinn, wenn Sie den konkreten Typ bereits instanziieren, aber Sie können den abstrakten Typ trotzdem zurückgeben, sodass der Client-Code glücklicherweise ignorant ist.
Jacob Raihle
4
Verwenden Sie keine Eltern / Kinder. Verwenden Sie Interface / Class (für Java).
Thorbjørn Ravn Andersen
4
Google "Programming to a Interface" bietet eine Reihe von Erklärungen, warum die Verwendung Parent x = new Child()eine gute Idee ist.
Kevin Workman

Antworten:

69

Es hängt vom Kontext ab, aber ich würde argumentieren, dass Sie den abstraktesten möglichen Typ angeben sollten. Auf diese Weise wird Ihr Code so allgemein wie möglich und hängt nicht von irrelevanten Details ab.

Ein Beispiel wäre ein LinkedListund von ArrayListdem beide abstammen List. Wenn der Code mit jeder Art von Liste gleich gut funktionieren würde, gibt es keinen Grund, ihn willkürlich auf eine der Unterklassen zu beschränken.

JacquesB
quelle
24
Genau. Denken Sie zum Hinzufügen eines Beispiels über Folgendes nach List list = new ArrayList(): Dem Code, der die Liste verwendet, ist es egal, um welche Art von Liste es sich handelt. Er erfüllt nur den Vertrag der Listenschnittstelle. In Zukunft könnten wir leicht zu einer anderen List-Implementierung wechseln, und der zugrunde liegende Code müsste überhaupt nicht geändert oder aktualisiert werden.
Vielleicht_Faktor
19
Gute Antwort, sollte aber erweitern, dass der abstrakteste mögliche Typ bedeutet, dass der Code im Gültigkeitsbereich nicht auf das untergeordnete Element zurückgeworfen werden sollte, um eine untergeordnete spezifische Operation auszuführen.
Mike
10
@Mike: Ich hoffe das versteht sich von selbst, sonst würden alle Variablen, Parameter und Felder als deklariert Object!
JacquesB
3
@JacquesB: Da dieser Frage und Antwort so viel Aufmerksamkeit geschenkt wurde, dachte ich, dass es für Menschen mit allen unterschiedlichen Qualifikationsniveaus nützlich wäre, explizit zu sein.
Mike
1
Es kommt darauf an, dass der Kontext keine Antwort ist.
Billal Begueradj
11

JacquesBs Antwort ist richtig, wenn auch ein wenig abstrakt. Lassen Sie mich einige Aspekte klarer hervorheben:

In Ihrem Code-Snippet verwenden Sie explizit das newSchlüsselwort und möchten möglicherweise mit weiteren Initialisierungsschritten fortfahren, die wahrscheinlich spezifisch für den konkreten Typ sind: Dann müssen Sie die Variable mit diesem spezifischen konkreten Typ deklarieren.

Die Situation ist anders, wenn Sie diesen Artikel von einem anderen Ort erhalten, z IFragment fragment = SomeFactory.GiveMeFragments(...);. Hier sollte die Factory normalerweise den abstraktesten Typ zurückgeben, und der Code nach unten sollte nicht von Implementierungsdetails abhängen.

Bernhard Hiller
quelle
18
"obwohl ein wenig abstrakt". Badumtish.
Rosuav
1
Hätte das nicht eine Bearbeitung sein können?
beppe9000
6

Es gibt möglicherweise Leistungsunterschiede.

Wenn die abgeleitete Klasse versiegelt ist, kann der Compiler virtuelle Aufrufe einbinden und optimieren oder eliminieren. Es kann also einen signifikanten Leistungsunterschied geben.

Beispiel: ToStringist ein virtueller Anruf, ist aber Stringversiegelt. Ein String, ToStringist ein No-Op. Wenn Sie also deklarieren, objectdass dies ein virtueller Aufruf ist, und wenn Sie deklarieren, dass Stringder Compiler weiß, dass keine abgeleitete Klasse die Methode überschrieben hat, weil die Klasse versiegelt ist, ist dies ein No-Op. Ähnliche Überlegungen gelten für ArrayListvs LinkedList.

Wenn Sie den konkreten Typ des Objekts kennen (und es keinen Kapselungsgrund gibt, ihn zu verbergen), sollten Sie diesen Typ deklarieren. Da Sie gerade das Objekt erstellt haben, kennen Sie den konkreten Typ.

Ben
quelle
4
In den meisten Fällen sind die Leistungsunterschiede geringfügig. Und in den meisten Fällen (ich habe gehört, dass dies in etwa 90% der Fälle der Fall ist) sollten wir diese kleinen Unterschiede vergessen. Nur wenn wir wissen (am besten durch Messung), dass wir als leistungskritischer Codeabschnitt altern, sollten wir uns um solche Optimierungen kümmern.
Jules
Und der Compiler weiß, dass "new Child ()" ein untergeordnetes Element zurückgibt, auch wenn es einem übergeordneten Element zugewiesen ist. Solange er dies weiß, kann er dieses Wissen verwenden und den Child () - Code aufrufen.
gnasher729
+1. Habe auch dein Beispiel in meiner Antwort gestohlen. = P
Nat
1
Beachten Sie, dass es Leistungsoptimierungen gibt, die das Ganze zum Thema machen. HotSpot stellt beispielsweise fest, dass ein an eine Funktion übergebener Parameter immer einer bestimmten Klasse angehört, und optimiert entsprechend. Sollte sich diese Annahme jemals als falsch herausstellen, wird die Methode desoptimiert. Dies hängt jedoch stark von der Compiler- / Laufzeitumgebung ab. Das letzte Mal, als ich die CLR überprüfte, konnte ich nicht einmal die eher triviale Optimierung durchführen, um festzustellen, dass p aus Parent p = new Child()immer ein Kind ist.
Voo
4

Der Hauptunterschied ist die erforderliche Zugriffsebene. Und jede gute Antwort auf die Frage der Abstraktion bezieht Tiere mit ein, wie mir gesagt wurde.

Nehmen wir an, Sie haben ein paar Tiere - Cat Birdund Dog. Nun, diese Tiere haben ein paar gemeinsamen Verhaltensweisen - move(), eat()und speak(). Sie alle essen anders und sprechen anders, aber wenn ich mein Tier zum Essen oder Sprechen brauche, ist es mir egal, wie sie es tun.

Aber manchmal mache ich. Ich habe nie eine Wachkatze oder einen Wachvogel hart, aber ich habe einen Wachhund. Wenn jemand in mein Haus einbricht, kann ich mich nicht einfach darauf verlassen, dass ich spreche, um den Eindringling abzuschrecken. Ich brauche wirklich meinen Hund zum Bellen - das ist anders als sein Sprechen, das ist nur ein Schuss.

In Code, der einen Eindringling erfordert, muss ich das wirklich tun Dog fido = getDog(); fido.barkMenancingly();

Aber die meiste Zeit kann ich Tiere glücklich dazu bringen, Tricks zu machen, bei denen sie nach Leckereien weinen, miauen oder twittern. Animal pet = getAnimal(); pet.speak();

Genauer gesagt, ArrayList hat einige Methoden, die Listdies nicht tun . Vor allem trimToSize(). In 99% der Fälle interessiert uns dies nicht. Das Listist fast nie groß genug, um uns zu kümmern. Aber es gibt Zeiten, in denen es so ist. Daher müssen wir gelegentlich ausdrücklich darum bitten ArrayList, dass a ausgeführt wird trimToSize()und das Backing-Array kleiner wird.

Beachten Sie, dass dies nicht bei der Erstellung gemacht werden muss - es könnte über eine Rückgabemethode oder sogar eine Besetzung erfolgen, wenn wir absolut sicher sind.

corsiKa
quelle
Verwirrt, warum dies eine Ablehnung bekommen könnte. Es scheint kanonisch, persönlich. Bis auf die Idee eines programmierbaren Wachhundes ...
corsiKa
Warum musste ich Seiten lesen, um die beste Antwort zu erhalten? Bewertung saugt.
Basilevs
Danke für die netten Worte. Die Bewertungssysteme haben Vor- und Nachteile, und eine davon ist, dass spätere Antworten manchmal im Staub bleiben können. Es passiert!
CorsiKa
2

Im Allgemeinen sollten Sie verwenden

var x = new Child(); // C#

oder

auto x = new Child(); // C++

für lokale Variablen, es sei denn, es gibt einen guten Grund dafür, dass die Variable einen anderen Typ als den hat, mit dem sie initialisiert wird.

(Hier ignoriere ich die Tatsache, dass der C ++ - Code höchstwahrscheinlich einen intelligenten Zeiger verwenden sollte. Es gibt Fälle, in denen nicht und ohne mehr als eine Zeile, die ich eigentlich nicht kennen kann.)

Die allgemeine Idee dabei ist, Sprachen zu verwenden, die die automatische Typerkennung von Variablen unterstützen, die das Lesen des Codes erleichtern und das Richtige tun (das heißt, das Typsystem des Compilers kann so weit wie möglich optimieren und die Initialisierung so ändern, dass sie neu ist Typ funktioniert auch, wenn der größte Teil der Zusammenfassung verwendet wurde.

Joshua
quelle
1
In C ++ werden Variablen meist ohne das neue Schlüsselwort erstellt: stackoverflow.com/questions/6500313/…
Marian Spanik
1
Die Frage bezieht sich nicht auf C # / C ++, sondern auf ein allgemeines objektorientiertes Problem in einer Sprache, die zumindest eine Form der statischen Typisierung aufweist (dh es wäre unsinnig, in etwas wie Ruby zu fragen). Außerdem sehe ich selbst in diesen beiden Sprachen keinen guten Grund, warum wir es tun sollten, wie Sie sagen.
AnoE
1

Gut, weil dieser Code einfach zu verstehen und zu ändern ist:

var x = new Child(); 
x.DoSomething();

Gut, weil es Absicht kommuniziert:

Parent x = new Child(); 
x.DoSomething(); 

Ok, weil es allgemein und leicht zu verstehen ist:

Child x = new Child(); 
x.DoSomething(); 

Die eine Option, die wirklich schlecht ist, ist zu verwenden, Parentwenn nur Childeine Methode hat DoSomething(). Es ist schlecht, weil es Absicht falsch kommuniziert:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

Lassen Sie uns nun etwas näher auf den Fall eingehen, zu dem folgende Fragen gestellt werden:

Parent x = new Child(); 
x.DoSomething(); 

Formatieren wir dies anders, indem wir die zweite Hälfte als Funktionsaufruf ausführen:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

Dies kann verkürzt werden auf:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

Ich gehe davon aus, dass es allgemein anerkannt ist, dass es WhichTypesich um den grundlegendsten / abstraktesten Typ handelt, mit dem die Funktion ausgeführt werden kann (sofern keine Leistungsprobleme vorliegen). In diesem Fall wäre der richtige Typ entweder Parentoder es Parentergibt sich etwas daraus.

Diese Argumentation erklärt, warum die Verwendung des Typs Parenteine gute Wahl ist, aber es spielt keine Rolle, ob die anderen Entscheidungen schlecht sind (sie sind es nicht).

Peter
quelle
1

tl; dr - Die Verwendung vonChildoverParentist im lokalen Bereich vorzuziehen. Dies trägt nicht nur zur Lesbarkeit bei, sondern auch dazu, dass die überladene Methodenauflösung ordnungsgemäß funktioniert und eine effiziente Kompilierung ermöglicht.


Im lokalen Bereich,

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

Konzeptionell geht es darum, die bestmöglichen Typinformationen zu erhalten. Wenn wir auf downgraden Parent, streichen wir im Wesentlichen nur Typinformationen heraus, die nützlich gewesen sein könnten.

Das Beibehalten der vollständigen Typinformationen hat vier Hauptvorteile:

  1. Stellt dem Compiler weitere Informationen zur Verfügung.
  2. Bietet dem Leser weitere Informationen.
  3. Sauberer, standardisierter Code.
  4. Macht die Programmlogik veränderlicher.

Vorteil 1: Mehr Informationen zum Compiler

Der scheinbare Typ wird bei überladener Methodenauflösung und bei der Optimierung verwendet.

Beispiel: Überladene Methodenauflösung

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

Im obigen Beispiel funktionieren beide foo()Aufrufe , aber in einem Fall erhalten wir die bessere Auflösung für überladene Methoden.

Beispiel: Compileroptimierung

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

Im obigen Beispiel .Foo()rufen beide Aufrufe letztendlich dieselbe zurückgegebene overrideMethode auf 2. Im ersten Fall gibt es eine virtuelle Methodensuche, um die richtige Methode zu finden. Diese Suche nach virtuellen Methoden ist im zweiten Fall nicht erforderlich, da diese Methoden vorhanden sind sealed.

Dank an @Ben, der in seiner Antwort ein ähnliches Beispiel lieferte .

Vorteil 2: Mehr Informationen für den Leser

Wenn Sie den genauen Typ kennen, dh wissen Child, wer den Code liest, erhalten Sie mehr Informationen, damit Sie besser sehen können, was das Programm tut.

Sicher, vielleicht ist es für den eigentlichen Code nicht wichtig, da beide parent.Foo();und child.Foo();Sinn machen, aber für jemanden, der den Code zum ersten Mal sieht, sind mehr Informationen einfach nur hilfreich.

Abhängig von Ihrer Entwicklungsumgebung kann die IDE möglicherweise hilfreichere QuickInfos und Metadaten bereitstellen Childals Parent.

Vorteil 3: Saubererer, standardisierterer Code

Die meisten C # -Codebeispiele, die ich in letzter Zeit gesehen habe, verwenden vardiese Abkürzung Child.

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

Das Anzeigen einer nicht vardeklarationspflichtigen Anweisung sieht einfach nicht so aus. Wenn es einen situationsbedingten Grund dafür gibt, ist es großartig, aber ansonsten sieht es nicht nach Muster aus.

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

Vorteil 4: Mehr veränderbare Programmlogik für das Prototyping

Die ersten drei Vorteile waren die großen; Das hier ist weitaus situativer.

Für Leute, die Code wie andere verwenden, die Excel verwenden, ändern wir jedoch ständig unseren Code. Möglicherweise müssen wir keine Methode aufrufen, die Childin dieser Version des Codes eindeutig ist , aber wir können den Code später erneut verwenden oder überarbeiten.

Der Vorteil eines starken Typsystems besteht darin, dass es uns bestimmte Metadaten über unsere Programmlogik liefert, wodurch die Möglichkeiten leichter ersichtlich werden. Dies ist beim Prototyping unglaublich nützlich, daher ist es am besten, es so weit wie möglich beizubehalten.

Zusammenfassung

Wenn Sie Parentdie überladene Methodenauflösung durcheinander bringen, werden einige Compiler-Optimierungen verhindert, Informationen aus dem Reader entfernt und der Code wird hässlicher.

Verwenden varist wirklich der richtige Weg. Es ist schnell, sauber, strukturiert und hilft dem Compiler und der IDE, ihre Arbeit richtig zu machen.


Wichtig : Diese Antwort bezieht sich aufParentvs.Childim lokalen Bereich einer Methode. Das Problem vonParentvs.Childist für Rückgabetypen, Argumente und Klassenfelder sehr unterschiedlich.

Nat
quelle
2
Ich würde hinzufügen, dass Parent list = new Child()das nicht viel Sinn macht. Der Code, der direkt die konkrete Instanz eines Objekts erstellt (im Gegensatz zur Indirektion durch Fabriken, Factory-Methoden usw.), enthält perfekte Informationen über den zu erstellenden Typ. Möglicherweise muss er mit der konkreten Schnittstelle interagieren, um das neu erstellte Objekt usw. zu konfigurieren. Im lokalen Bereich erreichen Sie keine Flexibilität. Flexibilität wird erreicht, wenn Sie das neu erstellte Objekt einem Kunden Ihrer Klasse über eine abstraktere Oberfläche zur Verfügung stellen (Fabriken und Factory-Methoden sind ein perfektes Beispiel)
crizzis
"tl; dr Die Verwendung von Child über Parent ist im lokalen Bereich vorzuziehen. Sie trägt nicht nur zur Lesbarkeit bei," - nicht unbedingt ... Wenn Sie die untergeordneten Details nicht verwenden, werden lediglich mehr Details als erforderlich hinzugefügt. "Es muss jedoch auch sichergestellt werden, dass die überladene Methodenauflösung ordnungsgemäß funktioniert und eine effiziente Kompilierung ermöglicht." - das hängt sehr von der programmiersprache ab. Es gibt viele, bei denen sich daraus keinerlei Probleme ergeben.
AnoE
1
Haben Sie eine Quelle , dass Parent p = new Child(); p.doSomething();und ein Child c = new Child; c.doSomething();anderes Verhalten haben? Vielleicht ist das in einer Sprache eine Eigenart, aber es soll sein, dass das Objekt das Verhalten steuert, nicht die Referenz. Das ist das Schöne am Erbe! Solange Sie darauf vertrauen können, dass untergeordnete Implementierungen den Vertrag für die übergeordnete Implementierung erfüllen, können Sie loslegen.
corsiKa
@corsiKa Ja, es gibt verschiedene Möglichkeiten. Zwei kurze Beispiele wären, wo Methodensignaturen überschrieben werden, z. B. mit dem newSchlüsselwort in C # , und wenn es mehrere Definitionen gibt, z. B. mit Mehrfachvererbung in C ++ .
Nat
@corsiKa Nur ein kurzes drittes Beispiel (weil ich denke, dass es ein guter Kontrast zwischen C ++ und C # ist). C # weist ein Problem wie das von C ++ auf, bei dem Sie dieses Verhalten auch von expliziten Schnittstellenmethoden abrufen können . Es ist lustig, weil Sprachen wie C # teilweise Schnittstellen anstelle von Mehrfachvererbung verwenden, um diese Probleme zu vermeiden. Durch die Übernahme von Schnittstellen sind sie jedoch immer noch Teil des Problems - aber es ist nicht ganz so schlimm, weil es in diesem Szenario nur zutrifft bis wann Parentist ein interface.
Nat
0

Vorausgesetzt parentType foo = new childType1();, Code kann nur übergeordnete Methoden verwenden foo, kann jedoch in fooeinem Verweis auf jedes Objekt parentgespeichert werden, dessen Typ von --nicht nur Instanzen von - abgeleitet ist childType1. Wenn die neue childType1Instanz die einzige Instanz ist, auf die foojemals Bezug genommen wird, ist es möglicherweise besser, den fooTyp als zu deklarieren childType1. Auf der anderen Seite können fooVerweise auf andere Typen erforderlich sein , wenn dies deklariert parentTypewird, um dies zu ermöglichen.

Superkatze
quelle
0

Ich schlage vor, mit

Child x = new Child();

anstatt

Parent x = new Child();

Letzterer verliert Informationen, Ersterer nicht.

Sie können alles mit dem ersteren machen, was Sie mit dem letzteren machen können, aber nicht umgekehrt.


Um den Zeiger weiter zu erläutern, haben Sie mehrere Vererbungsebenen.

Base-> DerivedL1->DerivedL2

Und Sie möchten das Ergebnis new DerivedL2()einer Variablen initialisieren . Bei Verwendung eines Basistyps haben Sie die Möglichkeit, Baseoder zu verwenden DerivedL1.

Sie können verwenden

Base x = new DerivedL2();

oder

DerivedL1 x = new DerivedL2();

Ich sehe keinen logischen Weg, das eine dem anderen vorzuziehen.

Wenn du benutzt

DerivedL2 x = new DerivedL2();

es gibt nichts zu streiten.

R Sahu
quelle
3
Weniger können, wenn man nicht mehr muss, ist eine gute Sache, oder?
Peter
1
@Peter, die Fähigkeit, weniger zu tun, ist nicht eingeschränkt. Es ist immer möglich. Wenn die Fähigkeit, mehr zu tun, eingeschränkt ist, kann dies ein wunder Punkt sein.
R Sahu
Aber manchmal ist es gut, Informationen zu verlieren. In Bezug auf Ihre Hierarchie würden die meisten Leute wahrscheinlich dafür stimmen Base(vorausgesetzt, es funktioniert). Sie bevorzugen die spezifischsten, AFAIK die Mehrheit bevorzugt die am wenigsten spezifischen. Ich würde sagen, dass es für lokale Variablen kaum wichtig ist.
Maaartinus
@maaartinus, ich bin überrascht, dass die Mehrheit der Benutzer es vorzieht, Informationen zu verlieren. Ich sehe die Vorteile des Informationsverlusts nicht so deutlich wie andere.
R Sahu
Wenn Sie deklarieren Base x = new DerivedL2();, sind Sie am meisten eingeschränkt und können so mit minimalem Aufwand ein anderes (Enkel-) Kind einschalten. Außerdem sieht man sofort, dass es möglich ist. +++ Sind wir uns einig, dass void f(Base)das besser ist als void f(DerivedL2)?
Maaartinus
0

Ich würde vorschlagen, Variablen immer als den spezifischsten Typ zurückzugeben oder zu speichern, aber Parameter als den allgemeinsten Typ zu akzeptieren.

Z.B

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

Die Parameter sind List<T>sehr allgemein gehalten. ArrayList<T>, LinkedList<T>und andere könnten alle von dieser Methode akzeptiert werden.

Wichtig ist, dass der Rückgabetyp LinkedHashMap<K, V>nicht ist Map<K, V>. Wenn jemand das Ergebnis dieser Methode einem zuweisen möchte Map<K, V>, kann er das tun. Es wird deutlich, dass dieses Ergebnis mehr als nur eine Karte ist, sondern eine Karte mit einer definierten Reihenfolge.

Angenommen, diese Methode wird zurückgegeben Map<K, V>. Wenn der Anrufer eine wünschte LinkedHashMap<K, V>, müsste er eine Typprüfung, Besetzung und Fehlerbehandlung durchführen, was wirklich umständlich ist.

Alexander - Setzen Sie Monica wieder ein
quelle
Dieselbe Logik, die Sie zwingt, einen breiten Typ als Parameter zu akzeptieren, sollte auch für den Rückgabetyp gelten. Wenn das Ziel der Funktion darin besteht, Schlüssel zu Werten zuzuordnen, sollte sie ein Mapnicht a zurückgeben LinkedHashMap- Sie möchten nur ein LinkedHashMapfür eine Funktion wie keyValuePairsToFifoMapoder so etwas zurückgeben. Weiter ist besser, bis Sie einen bestimmten Grund haben, dies nicht zu tun.
corsiKa
@corsiKa Hmm, ich stimme zu, das ist ein besserer Name, aber dies ist nur ein Beispiel. Ich bin nicht der Meinung, dass es (im Allgemeinen) besser ist, Ihre Renditeart zu erweitern. Das künstliche Entfernen von Typinformationen ohne Begründung. Wenn Sie sich vorstellen, dass Ihr Benutzer den allgemeinen Typ, den Sie ihm zur Verfügung gestellt haben, vernünftigerweise zurückwerfen muss, haben Sie ihm einen schlechten Dienst erwiesen.
Alexander - Reinstate Monica
"ohne Rechtfertigung" - aber es gibt Rechtfertigung! Wenn ich mit der Rückgabe eines breiteren Typs durchkomme, wird das Refactoring später einfacher. Wenn jemand den bestimmten Typ aufgrund eines Verhaltens benötigt, bin ich alle dafür, den entsprechenden Typ zurückzugeben. Aber ich möchte nicht an einen bestimmten Typ gebunden sein, wenn ich es nicht muss. Ich möchte die Möglichkeit haben, die Details meiner Methode zu ändern, ohne die zu beschädigen, die sie verwenden, und wenn ich sie aus irgendeinem Grund von, sagen wir LinkedHashMap, ändere TreeMap, muss ich jetzt eine Menge Dinge ändern.
corsiKa
Eigentlich stimme ich dem bis zum letzten Satz zu. Sie haben von LinkedHashMapzu geändert , TreeMapist keine triviale Änderung, z. B. von ArrayListzu LinkedList. Es ist eine Veränderung von semantischer Bedeutung. In diesem Fall Sie wollen die Compiler einen Fehler zu werfen, die Verbraucher über den Rückgabewert zu zwingen , die Änderung und entsprechende Maßnahmen ergreifen zu bemerken.
Alexander - Wiedereinsetzung von Monica
Aber es kann eine triviale Änderung sein, wenn Sie sich nur um das Kartenverhalten kümmern (und wenn Sie können, sollten Sie es tun). Wenn der Vertrag "Sie erhalten eine Schlüsselwertkarte" lautet und die Reihenfolge keine Rolle spielt (was für gilt) die meisten Karten), dann ist es egal, ob es sich um eine Baum- oder eine Hash-Karte handelt. Zum Beispiel: Thread#getAllStackTraces()- gibt a Map<Thread,StackTraceElement[]>anstelle von a zurück, HashMapdamit sie es in einen beliebigen Typ ändern können Map.
corsiKa