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?
Parent x = new Child()
eine gute Idee ist.Antworten:
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
LinkedList
und vonArrayList
dem beide abstammenList
. 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.quelle
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.Object
!JacquesBs Antwort ist richtig, wenn auch ein wenig abstrakt. Lassen Sie mich einige Aspekte klarer hervorheben:
In Ihrem Code-Snippet verwenden Sie explizit das
new
Schlü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.quelle
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:
ToString
ist ein virtueller Anruf, ist aberString
versiegelt. EinString
,ToString
ist ein No-Op. Wenn Sie also deklarieren,object
dass dies ein virtueller Aufruf ist, und wenn Sie deklarieren, dassString
der Compiler weiß, dass keine abgeleitete Klasse die Methode überschrieben hat, weil die Klasse versiegelt ist, ist dies ein No-Op. Ähnliche Überlegungen gelten fürArrayList
vsLinkedList
.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.
quelle
Parent p = new Child()
immer ein Kind ist.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
Bird
undDog
. Nun, diese Tiere haben ein paar gemeinsamen Verhaltensweisen -move()
,eat()
undspeak()
. 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
List
dies nicht tun . Vor allemtrimToSize()
. In 99% der Fälle interessiert uns dies nicht. DasList
ist 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 bittenArrayList
, dass a ausgeführt wirdtrimToSize()
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.
quelle
Im Allgemeinen sollten Sie verwenden
oder
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.
quelle
Gut, weil dieser Code einfach zu verstehen und zu ändern ist:
Gut, weil es Absicht kommuniziert:
Ok, weil es allgemein und leicht zu verstehen ist:
Die eine Option, die wirklich schlecht ist, ist zu verwenden,
Parent
wenn nurChild
eine Methode hatDoSomething()
. Es ist schlecht, weil es Absicht falsch kommuniziert:Lassen Sie uns nun etwas näher auf den Fall eingehen, zu dem folgende Fragen gestellt werden:
Formatieren wir dies anders, indem wir die zweite Hälfte als Funktionsaufruf ausführen:
Dies kann verkürzt werden auf:
Ich gehe davon aus, dass es allgemein anerkannt ist, dass es
WhichType
sich 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 entwederParent
oder esParent
ergibt sich etwas daraus.Diese Argumentation erklärt, warum die Verwendung des Typs
Parent
eine gute Wahl ist, aber es spielt keine Rolle, ob die anderen Entscheidungen schlecht sind (sie sind es nicht).quelle
tl; dr - Die Verwendung von
Child
overParent
ist 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,
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:
Vorteil 1: Mehr Informationen zum Compiler
Der scheinbare Typ wird bei überladener Methodenauflösung und bei der Optimierung verwendet.
Beispiel: Überladene Methodenauflösung
Im obigen Beispiel funktionieren beide
foo()
Aufrufe , aber in einem Fall erhalten wir die bessere Auflösung für überladene Methoden.Beispiel: Compileroptimierung
Im obigen Beispiel
.Foo()
rufen beide Aufrufe letztendlich dieselbe zurückgegebeneoverride
Methode auf2
. 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 sindsealed
.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();
undchild.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
Child
alsParent
.Vorteil 3: Saubererer, standardisierterer Code
Die meisten C # -Codebeispiele, die ich in letzter Zeit gesehen habe, verwenden
var
diese AbkürzungChild
.Das Anzeigen einer nicht
var
deklarationspflichtigen 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.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
Child
in 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
Parent
die überladene Methodenauflösung durcheinander bringen, werden einige Compiler-Optimierungen verhindert, Informationen aus dem Reader entfernt und der Code wird hässlicher.Verwenden
var
ist 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 auf
Parent
vs.Child
im lokalen Bereich einer Methode. Das Problem vonParent
vs.Child
ist für Rückgabetypen, Argumente und Klassenfelder sehr unterschiedlich.quelle
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)Parent p = new Child(); p.doSomething();
und einChild 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.new
Schlüsselwort in C # , und wenn es mehrere Definitionen gibt, z. B. mit Mehrfachvererbung in C ++ .Parent
ist eininterface
.Vorausgesetzt
parentType foo = new childType1();
, Code kann nur übergeordnete Methoden verwendenfoo
, kann jedoch infoo
einem Verweis auf jedes Objektparent
gespeichert werden, dessen Typ von --nicht nur Instanzen von - abgeleitet istchildType1
. Wenn die neuechildType1
Instanz die einzige Instanz ist, auf diefoo
jemals Bezug genommen wird, ist es möglicherweise besser, denfoo
Typ als zu deklarierenchildType1
. Auf der anderen Seite könnenfoo
Verweise auf andere Typen erforderlich sein , wenn dies deklariertparentType
wird, um dies zu ermöglichen.quelle
Ich schlage vor, mit
anstatt
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,Base
oder zu verwendenDerivedL1
.Sie können verwenden
oder
Ich sehe keinen logischen Weg, das eine dem anderen vorzuziehen.
Wenn du benutzt
es gibt nichts zu streiten.
quelle
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.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, dassvoid f(Base)
das besser ist alsvoid f(DerivedL2)
?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
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 istMap<K, V>
. Wenn jemand das Ergebnis dieser Methode einem zuweisen möchteMap<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ünschteLinkedHashMap<K, V>
, müsste er eine Typprüfung, Besetzung und Fehlerbehandlung durchführen, was wirklich umständlich ist.quelle
Map
nicht a zurückgebenLinkedHashMap
- Sie möchten nur einLinkedHashMap
für eine Funktion wiekeyValuePairsToFifoMap
oder so etwas zurückgeben. Weiter ist besser, bis Sie einen bestimmten Grund haben, dies nicht zu tun.LinkedHashMap
, ändereTreeMap
, muss ich jetzt eine Menge Dinge ändern.LinkedHashMap
zu geändert ,TreeMap
ist keine triviale Änderung, z. B. vonArrayList
zuLinkedList
. 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.Thread#getAllStackTraces()
- gibt aMap<Thread,StackTraceElement[]>
anstelle von a zurück,HashMap
damit sie es in einen beliebigen Typ ändern könnenMap
.