Angenommen, eine Sprache mit einer gewissen inhärenten Typensicherheit (z. B. kein JavaScript):
Ausgehend von einer Methode, die a akzeptiert SuperType
, wissen wir, dass in den meisten Fällen die Versuchung besteht, Typprüfungen durchzuführen, um eine Aktion auszuwählen:
public void DoSomethingTo(SuperType o) {
if (o isa SubTypeA) {
o.doSomethingA()
} else {
o.doSomethingB();
}
}
Wir sollten normalerweise, wenn nicht immer, eine einzelne, überschreibbare Methode auf dem erstellen SuperType
und dies tun:
public void DoSomethingTo(SuperType o) {
o.doSomething();
}
... wobei jeder Subtyp eine eigene doSomething()
Implementierung erhält . Der Rest unserer Anwendung kann dann in geeigneter Weise unwissend sein, ob eine gegebene SuperType
wirklich eine SubTypeA
oder eine ist SubTypeB
.
Wunderbar
Wir erhalten jedoch immer noch is a
ähnliche Operationen in den meisten, wenn nicht allen typsicheren Sprachen. Dies deutet darauf hin, dass möglicherweise eine explizite Typprüfung erforderlich ist.
In welchen Situationen sollten oder müssen wir explizite Typprüfungen durchführen?
Vergib mir meine geistige Abwesenheit oder meinen Mangel an Kreativität. Ich weiß, dass ich es schon einmal getan habe. Aber es ist ehrlich gesagt so lange her, dass ich mich nicht erinnern kann, ob das, was ich getan habe, gut war! Und in den letzten Speicher, ich glaube nicht , dass ich je gesehen habe Notwendigkeit Arten außerhalb meines Cowboy JavaScript zu testen.
quelle
Antworten:
"Niemals" lautet die kanonische Antwort auf "Wann ist die Typprüfung in Ordnung?" Es gibt keine Möglichkeit, dies zu beweisen oder zu widerlegen. Es ist Teil eines Glaubenssystems darüber, was "gutes Design" oder "gutes objektorientiertes Design" ausmacht. Es ist auch hokum.
Um sicherzugehen, wenn Sie eine integrierte Menge von Klassen und mehr als eine oder zwei Funktionen haben, die diese Art der direkten Typprüfung benötigen, tun Sie es wahrscheinlich falsch. Was Sie wirklich brauchen, ist eine Methode, die in
SuperType
und in ihren Untertypen unterschiedlich implementiert ist . Dies ist ein wesentlicher Bestandteil der objektorientierten Programmierung, und die gesamten Grundklassen und Vererbungen existieren.In diesem Fall ist ein expliziter Typentest falsch, nicht weil der Typentest von Natur aus falsch ist, sondern weil die Sprache bereits eine saubere, erweiterbare und idiomatische Methode zur Typunterscheidung bietet und Sie sie nicht verwendet haben. Stattdessen sind Sie auf eine primitive, fragile, nicht erweiterbare Sprache zurückgefallen.
Lösung: Verwenden Sie die Redewendung. Fügen Sie, wie Sie vorgeschlagen haben, jeder Klasse eine Methode hinzu, und lassen Sie dann die Standardvererbungs- und Methodenauswahlalgorithmen bestimmen, welcher Fall zutrifft. Wenn Sie die Basistypen nicht ändern können, fügen Sie dort Ihre Methode hinzu.
Soviel zur konventionellen Weisheit und zu einigen Antworten. Einige Fälle, in denen explizite Typprüfungen sinnvoll sind:
Es ist ein Unikat. Wenn Sie viel Typunterscheidung zu tun hatten, könnten Sie die Typen oder Unterklassen erweitern. Aber du nicht. Sie haben nur ein oder zwei Stellen, an denen Sie explizite Tests benötigen. Es lohnt sich also nicht, die Klassenhierarchie noch einmal durchzuarbeiten, um die Funktionen als Methoden hinzuzufügen. Oder es ist den praktischen Aufwand nicht wert, die Art der Allgemeinheit, Tests, Entwurfsprüfungen, Dokumentation oder andere Attribute der Basisklassen für eine derart einfache, eingeschränkte Verwendung hinzuzufügen. In diesem Fall ist das Hinzufügen einer Funktion zum direkten Testen sinnvoll.
Sie können die Klassen nicht anpassen. Sie denken über Unterklassen nach - aber Sie können nicht. Beispielsweise sind viele Klassen in Java vorgesehen
final
. Sie versuchen, ein einzuspielen,public class ExtendedSubTypeA extends SubTypeA {...}
und der Compiler teilt Ihnen mit, dass das, was Sie tun, nicht möglich ist. Entschuldigung, Anmut und Raffinesse des objektorientierten Modells! Jemand hat entschieden, dass Sie ihre Typen nicht erweitern können! Leider gibt es viele Standardbibliothekenfinal
, und das Erstellen von Klassenfinal
ist eine gängige Gestaltungshilfe. Ein Funktionsabschluss ist das, was Ihnen noch zur Verfügung steht.Übrigens ist dies nicht auf statisch typisierte Sprachen beschränkt. Dynamische Sprache Python verfügt über eine Reihe von Basisklassen, die unter den in C implementierten Bedingungen nicht wirklich geändert werden können. Dies umfasst wie Java die meisten Standardtypen.
Ihr Code ist extern. Sie entwickeln mit Klassen und Objekten, die von einer Reihe von Datenbankservern, Middleware-Engines und anderen Codebasen stammen, die Sie nicht steuern oder anpassen können. Ihr Code ist nur ein geringer Konsument von Objekten, die an anderer Stelle generiert wurden. Selbst wenn Sie eine Unterklasse
SuperType
erstellen könnten , sind Sie nicht in der Lage, die Bibliotheken abzurufen, von denen Sie abhängig sind, um Objekte in Ihren Unterklassen zu generieren. Sie geben Ihnen Instanzen der Typen, die sie kennen, und nicht Ihre Varianten. Dies ist nicht immer der Fall ... manchmal sind sie auf Flexibilität ausgelegt und instanziieren dynamisch Instanzen von Klassen, die Sie ihnen zuweisen. Oder sie bieten einen Mechanismus zum Registrieren der Unterklassen, die ihre Fabriken erstellen sollen. XML-Parser scheinen besonders gut darin zu sein, solche Einstiegspunkte bereitzustellen. siehe zb oder lxml in Python . Die meisten Codebasen bieten jedoch keine solchen Erweiterungen an. Sie werden Ihnen die Klassen zurückgeben, mit denen sie gebaut wurden und über die Sie Bescheid wissen. Im Allgemeinen ist es nicht sinnvoll, deren Ergebnisse in Ihre benutzerdefinierten Ergebnisse zu übernehmen, nur damit Sie eine rein objektorientierte Typenauswahl verwenden können. Wenn Sie Typendiskriminierung betreiben, müssen Sie dies relativ grob tun. Ihr Typprüfcode sieht dann ganz passend aus.Generika für Arme / Mehrfachversand. Sie möchten eine Vielzahl unterschiedlicher Typen in Ihren Code aufnehmen und sind der Meinung, dass ein Array von sehr typspezifischen Methoden nicht sinnvoll ist.
public void add(Object x)
logisch erscheint, aber keine von ArrayaddByte
,addShort
,addInt
,addLong
,addFloat
,addDouble
,addBoolean
,addChar
, undaddString
Varianten (um nur einige zu nennen). Mit Funktionen oder Methoden, die einen hohen Supertyp aufweisen und dann von Typ zu Typ entscheiden, was zu tun ist, werden Sie beim jährlichen Booch-Liskov Design Symposium nicht mit dem Purity Award ausgezeichnet, sondern mit dem Durch die ungarische Benennung erhalten Sie eine einfachere API. In gewissem Sinne Ihris-a
oderis-instance-of
Beim Testen werden generische oder Mehrfachversendungen in einem Sprachkontext simuliert, der dies nicht nativ unterstützt.Durch die integrierte Sprachunterstützung für Generika und das Eingeben von Enten wird die Notwendigkeit einer Typprüfung verringert, indem die Wahrscheinlichkeit erhöht wird, dass "etwas Anmutiges und Angemessenes getan wird". Die in Sprachen wie Julia und Go vorkommende Mehrfachauswahl für Versand und Schnittstellen ersetzt in ähnlicher Weise die direkte Typprüfung durch integrierte Mechanismen für die typbasierte Auswahl von "was zu tun ist". Aber nicht alle Sprachen unterstützen diese. Java zB ist in der Regel Single-Dispatch und seine Redewendungen sind nicht besonders einfach zu tippen.
Aber selbst bei all diesen Funktionen zur Typunterscheidung - Vererbung, Generika, Entenschreiben und Mehrfachversand - ist es manchmal einfach bequem, eine einzige, konsolidierte Routine zu haben, die die Tatsache berücksichtigt, dass Sie etwas basierend auf dem Typ des Objekts tun klar und unmittelbar. Bei der Metaprogrammierung habe ich festgestellt, dass dies im Wesentlichen unvermeidbar ist. Ob das Zurückgreifen auf direkte Typanfragen "Pragmatismus in Aktion" oder "Dirty Coding" bedeutet, hängt von Ihrer Designphilosophie und -überzeugung ab.
quelle
(1.0).Equals(1.0f)
Nachgeben von true [Argument wird zudouble
],(1.0f).Equals(1.0)
Nachgeben von false [Argument wird zuobject
]; in Java,Math.round(123456789*1.0)
ergibt 123456789, sondernMath.round(123456789*1.0)
liefert 123456792 [Argument fördert , umfloat
stattdouble
].Math.round
sehen bei mir identisch aus. Was ist der Unterschied?Math.round(123456789)
[ein Hinweis darauf, was passieren könnte, wenn jemandMath.round(thing.getPosition() * COUNTS_PER_MIL)
einen nicht skalierten Positionswert zurückgibt und nicht merkt, dassgetPosition
einint
oder zurückgegeben wirdlong
.]Die Hauptsituation, die ich jemals gebraucht habe, war beim Vergleichen von zwei Objekten, z. B. in einer
equals(other)
Methode, die je nach Typ unterschiedliche Algorithmen erfordern kannother
. Sogar dann ist es ziemlich selten.Die andere Situation, auf die ich es wieder sehr selten kommen lassen habe, ist nach der Deserialisierung oder dem Parsen, wo Sie es manchmal benötigen, um sicher auf einen spezifischeren Typ zu gießen.
Manchmal brauchen Sie auch nur einen Hack, um Code von Drittanbietern zu umgehen, den Sie nicht kontrollieren. Es ist eines der Dinge, die Sie nicht wirklich regelmäßig verwenden möchten, aber Sie sind froh, dass es da ist, wenn Sie es wirklich brauchen.
quelle
BaseClass base = deserialize(input)
, weil Sie den Typ noch nicht kennen, undif (base instanceof Derived) derived = (Derived)base
speichern ihn dann als den genau abgeleiteten Typ.Der Standardfall (aber hoffentlich selten) sieht so aus: wenn in der folgenden Situation
die Funktionen
DoSomethingA
oderDoSomethingB
können nicht einfach als Mitgliedsfunktionen des Vererbungsbaums vonSuperType
/SubTypeA
/ implementiert werdenSubTypeB
. Zum Beispiel, wennDoSomethingXXX
diese Bibliothek hinzufügen , würde dies bedeuten, dass eine verbotene Abhängigkeit eingeführt wird.Beachten Sie, dass es häufig Situationen gibt, in denen Sie dieses Problem umgehen können (z. B. durch Erstellen eines Wrappers oder Adapters für
SubTypeA
undSubTypeB
oder durch den Versuch einerDoSomething
vollständigen Neuimplementierung in Bezug auf grundlegende Vorgänge vonSuperType
), aber manchmal sind diese Lösungen den Aufwand oder die Mühe nicht wert Dinge komplizierter und weniger erweiterbar als die explizite Typprüfung.Ein Beispiel aus meiner gestrigen Arbeit: Ich hatte eine Situation, in der ich die Verarbeitung einer Liste von Objekten (vom Typ
SuperType
mit genau zwei verschiedenen Untertypen, bei denen es äußerst unwahrscheinlich ist, dass es jemals mehr geben wird) parallelisieren wollte . Die unvergleichliche Version enthielt zwei Schleifen: eine Schleife für Objekte des Subtyps A mit AufrufDoSomethingA
und eine zweite Schleife für Objekte des Subtyps B mit AufrufDoSomethingB
.Die Methoden "DoSomethingA" und "DoSomethingB" sind zeitintensive Berechnungen, bei denen Kontextinformationen verwendet werden, die im Bereich der Subtypen A und B nicht verfügbar sind (daher ist es nicht sinnvoll, sie als Elementfunktionen der Subtypen zu implementieren). Aus der Sicht der neuen "Parallelschleife" vereinfacht sich die Arbeit erheblich, da sie einheitlich behandelt wird. Daher habe ich eine Funktion implementiert, die der
DoSomethingTo
von oben ähnelt . Ein Blick auf die Implementierungen von "DoSomethingA" und "DoSomethingB" zeigt jedoch, dass sie intern sehr unterschiedlich funktionieren. Der Versuch, ein generisches "DoSomething" durch ErweiterungSuperType
mit vielen abstrakten Methoden zu implementieren , würde also nicht wirklich funktionieren oder bedeuten, die Dinge komplett zu überarbeiten.quelle
SuperType
und es ist Subklassen?NSJSONSerialization
in Obj-C). Sie möchten jedoch nicht einfach darauf vertrauen, dass die Antwort den erwarteten Typ enthält. Überprüfen Sie sie daher, bevor Sie sie verwenden (z. B.if ([theResponse isKindOfClass:[NSArray class]])...
). .Wie Onkel Bob es nennt:
In einer seiner Clean Coder-Episoden gab er ein Beispiel für einen Funktionsaufruf, mit dem
Employee
s zurückgegeben wird.Manager
ist ein Untertyp vonEmployee
. Nehmen wir an, wir haben einen Anwendungsdienst, der dieManager
ID eines Benutzers akzeptiert und ihn ins Büro ruft :) Die Funktion gibtgetEmployeeById()
einen Supertyp zurückEmployee
, aber ich möchte überprüfen, ob in diesem Anwendungsfall ein Manager zurückgegeben wird.Zum Beispiel:
Hier überprüfe ich, ob es sich bei dem von der Abfrage zurückgegebenen Mitarbeiter tatsächlich um einen Manager handelt (dh ich erwarte, dass es sich um einen Manager handelt und ob dies ansonsten schnell fehlschlägt).
Nicht das beste Beispiel, aber es ist doch Onkel Bob.
Aktualisieren
Ich habe das Beispiel aktualisiert, so weit ich mich erinnern kann.
quelle
Manager
‚s Umsetzungsummon()
nur die Ausnahme in diesem Beispiel werfen?CEO
kann derManager
s beschwören .Employee
, sollte es nur wichtig sein, dass er etwas erhält, das sich wie ein verhältEmployee
. Wenn verschiedene UnterklassenEmployee
unterschiedliche Berechtigungen, Verantwortlichkeiten usw. haben, was macht die Typprüfung zu einer besseren Option als ein echtes Berechtigungssystem?Noch nie.
is
Klausel oder (in einigen Sprachen oder abhängig von der) ändern Szenario), da Sie den Typ nicht erweitern können, ohne die Interna der Funktion zu ändern, die dieis
Prüfung durchführt.is
Schecks ein starkes Zeichen dafür sind, dass Sie gegen das Liskov-Substitutionsprinzip verstoßen . Alles, was damit funktioniert,SuperType
sollte völlig unbekannt sein, welche Subtypen es möglicherweise gibt., Die alle gesagt,
is
kann überprüft werden weniger schlecht als andere Alternativen. Das Einordnen aller gängigen Funktionen in eine Basisklasse ist umständlich und führt häufig zu schlimmeren Problemen. Die Verwendung einer einzelnen Klasse mit einem Flag oder einer Aufzählung für den Typ der Instanz ist schlimmer als schrecklich, da Sie die Umgehung des Typsystems jetzt auf alle Konsumenten ausdehnen.Kurz gesagt, Sie sollten Typüberprüfungen immer als starken Codegeruch betrachten. Aber wie bei allen Richtlinien wird es Zeiten geben, in denen Sie sich entscheiden müssen, welche Richtlinienverletzung am wenigsten anstößig ist.
quelle
instanceof
leckt Implementierungsdetails und unterbricht die Abstraktion.IEnumerable<T>
verspricht nicht, dass ein "letztes" Element existiert. Wenn Ihre Methode ein solches Element benötigt, sollte ein Typ erforderlich sein, der die Existenz eines solchen Elements garantiert. Und die Subtypen dieses Typs können dann effiziente Implementierungen der "Last" -Methode liefern.Wenn Sie eine große Codebasis (über 100 KByte Codezeilen) haben und in der Nähe des Versands sind oder in einer Zweigstelle arbeiten, die später zusammengeführt werden muss, ist die Änderung einer großen Menge an Code mit hohen Kosten und Risiken verbunden.
Sie haben dann manchmal die Möglichkeit, einen großen Refraktor des Systems oder eine einfache, lokalisierte „Typprüfung“ durchzuführen. Dadurch entsteht eine technische Verschuldung, die schnellstmöglich zurückgezahlt werden sollte, aber häufig nicht.
(Es ist unmöglich, ein Beispiel zu finden, da jeder Code, der klein genug ist, um als Beispiel verwendet zu werden, auch klein genug ist, damit das bessere Design klar sichtbar ist.)
Oder mit anderen Worten, wenn das Ziel darin besteht, Ihren Lohn zu zahlen, anstatt für die Sauberkeit Ihres Designs „up votes“ zu erhalten.
Der andere häufige Fall ist der UI-Code, wenn Sie zum Beispiel für einige Arten von Mitarbeitern eine andere UI anzeigen, aber natürlich nicht möchten, dass die UI-Konzepte in alle Domänenklassen übergehen.
Mit "Typentest" können Sie entscheiden, welche Version der Benutzeroberfläche angezeigt werden soll, oder Sie können eine ausgefallene Nachschlagetabelle verwenden, die von "Domänenklassen" in "Benutzeroberflächenklassen" konvertiert. Die Nachschlagetabelle ist nur eine Möglichkeit, die „Typprüfung“ an einem Ort zu verbergen.
(Der Datenbankaktualisierungscode kann dieselben Probleme wie der Benutzeroberflächencode haben, Sie verfügen jedoch in der Regel nur über einen Satz von Datenbankaktualisierungscode. Sie können jedoch viele verschiedene Bildschirme verwenden, die an den angezeigten Objekttyp angepasst werden müssen.)
quelle
Die Implementierung von LINQ verwendet eine Vielzahl von Typprüfungen für mögliche Leistungsoptimierungen und dann einen Fallback für IEnumerable.
Das offensichtlichste Beispiel ist wahrscheinlich die ElementAt-Methode (winziger Auszug aus dem .NET 4.5-Quellcode):
Es gibt jedoch viele Stellen in der Enumerable-Klasse, an denen ein ähnliches Muster verwendet wird.
Daher ist die Optimierung der Leistung für einen häufig verwendeten Subtyp möglicherweise eine gültige Verwendung. Ich bin mir nicht sicher, wie dies besser hätte gestaltet werden können.
quelle
IEnumerable<T>
viele Methoden wie die inList<T>
zusammen mit einerFeatures
Eigenschaft enthalten sind, die angibt, von welchen Methoden erwartet werden kann, dass sie gut, langsam oder überhaupt nicht funktionieren. sowie verschiedene Annahmen, die ein Verbraucher in Bezug auf die Sammlung sicher treffen kann (z. B. wird garantiert, dass sich die Größe und / oder der vorhandene Inhalt der Sammlung nicht ändern (ein Typ könnte dies unterstützenAdd
und dennoch garantieren, dass der vorhandene Inhalt unveränderlich ist)).Es gibt ein Beispiel, das in Spieleentwicklungen häufig vorkommt, insbesondere bei der Kollisionserkennung, und das ohne die Verwendung einer Art von Typprüfung schwer zu handhaben ist.
Angenommen, alle Spielobjekte stammen von einer gemeinsamen Basisklasse
GameObject
. Jedes Objekt hat eine starre Körper Kollisionsform ,CollisionShape
die eine gemeinsame Schnittstelle zur Verfügung stellen kann (query Position zu sagen, Orientierung, etc.) , aber die eigentlichen Kollisionsformen werden alle konkrete Subklassen sein , wie beispielsweiseSphere
,Box
,ConvexHull
usw. Speicher von Informationen , die spezifisch für diese Art von geometrischem Objekt (siehe hier für ein reales Beispiel)Um nun auf Kollisionen zu testen, muss ich eine Funktion für jedes Paar von Kollisionsformtypen schreiben:
Diese enthalten die spezifische Mathematik, die zum Durchführen einer Schnittmenge dieser beiden geometrischen Typen erforderlich ist.
Bei jedem Tick meiner Spielrunde muss ich zwei Objekte auf Kollisionen prüfen. Ich habe aber nur Zugriff auf
GameObject
s und die entsprechendenCollisionShape
s. Natürlich muss ich konkrete Typen kennen, um zu wissen, welche Kollisionserkennungsfunktion aufgerufen werden soll. Hier kann nicht einmal der doppelte Versand helfen (was logischerweise nichts anderes ist, als den Typ zu überprüfen) *.In der Praxis verlassen sich die von mir gesehenen Physik-Motoren (Bullet und Havok) in dieser Situation auf Typprüfungen der einen oder anderen Form.
Ich sage nicht, dass dies notwendigerweise eine gute Lösung ist, es ist nur so, dass es das Beste aus einer kleinen Anzahl möglicher Lösungen für dieses Problem ist
* Technisch es ist möglich , Doppel - Ausgabe in einer schrecklichen und komplizierten Art und Weise zu verwenden , die N (N + 1) / 2 Kombinationen erfordern würden (wobei N die Anzahl von Formtypen Sie haben) zur Verfügung und würde nur verschleiern , was Sie wirklich tun was Indem ich gleichzeitig die Typen der beiden Formen herausfinde, halte ich dies nicht für eine realistische Lösung.
quelle
Manchmal möchten Sie nicht allen Klassen eine gemeinsame Methode hinzufügen, da es nicht in ihrer Verantwortung liegt, diese bestimmte Aufgabe auszuführen.
Zum Beispiel möchten Sie einige Objekte zeichnen, aber den Zeichnungscode nicht direkt hinzufügen (was sinnvoll ist). In Sprachen, die keinen Mehrfachversand unterstützen, erhalten Sie möglicherweise den folgenden Code:
Dies ist problematisch, wenn dieser Code an mehreren Stellen angezeigt wird und Sie ihn überall ändern müssen, wenn Sie einen neuen Entitätstyp hinzufügen. Wenn dies der Fall ist, kann dies durch die Verwendung des Besuchermusters vermieden werden. Manchmal ist es jedoch besser, die Dinge einfach zu halten und nicht zu überdenken. In diesen Situationen ist die Typprüfung in Ordnung.
quelle
Ich benutze nur die Kombination mit Reflektion. Aber selbst dann ist die dynamische Prüfung meistens nicht fest auf eine bestimmte Klasse festgelegt (oder nur fest auf spezielle Klassen wie
String
oder festgelegtList
).Mit dynamischer Überprüfung meine ich:
und nicht fest codiert
quelle
Typprüfung und Typguss sind zwei sehr eng verwandte Konzepte. So eng verwandt, dass ich zuversichtlich bin, dass Sie niemals eine Typprüfung durchführen sollten, es sei denn, Sie möchten das Objekt basierend auf dem Ergebnis tippen.
Wenn denken Sie über ideale Objektorientiertes Design, Typprüfung (und Gießen) sollte nie passieren. Aber hoffentlich haben Sie inzwischen herausgefunden, dass objektorientierte Programmierung nicht ideal ist. Manchmal, insbesondere bei Code auf niedrigerer Ebene, kann der Code nicht dem Ideal entsprechen. Dies ist bei ArrayLists in Java der Fall. Da sie zur Laufzeit nicht wissen, welche Klasse im Array gespeichert ist, erstellen sie
Object[]
Arrays und wandeln sie statisch in den richtigen Typ um.Es wurde darauf hingewiesen, dass ein allgemeines Bedürfnis nach Typentests (und Typguss) von der
Equals
Methode herrührt, die in den meisten Sprachen eine Ebene annehmen sollObject
. In der Implementierung sollten einige detaillierte Überprüfungen vorgenommen werden, um festzustellen, ob die beiden Objekte vom selben Typ sind, und um zu testen, um welchen Typ es sich handelt.Auch die Typprüfung spiegelt sich häufig wider. Oft haben Sie Methoden, die zurückgeben,
Object[]
oder ein anderes generisches Array, und Sie möchten alleFoo
Objekte aus irgendeinem Grund herausziehen . Dies ist eine absolut legitime Verwendung der Typprüfung und des Gießens.Im Allgemeinen sind Typentests schlecht, wenn der Code unnötigerweise an die Art und Weise gekoppelt wird, in der eine bestimmte Implementierung geschrieben wurde. Dies kann leicht dazu führen, dass für jeden Typ oder jede Kombination von Typen ein spezifischer Test erforderlich ist, z. B. wenn Sie den Schnittpunkt von Linien, Rechtecken und Kreisen ermitteln möchten und die Schnittpunktfunktion für jede Kombination einen anderen Algorithmus hat. Ihr Ziel ist es, Details, die für eine Art von Objekt spezifisch sind, an derselben Stelle wie dieses Objekt zu platzieren, da dies die Pflege und Erweiterung Ihres Codes erleichtert.
quelle
ArrayLists
Ich kenne die zur Laufzeit gespeicherte Klasse nicht, da Java keine Generics hatte und Oracle sich bei der endgültigen Einführung für die Abwärtskompatibilität mit generics-losem Code entschieden hat.equals
hat das gleiche Problem, und es ist sowieso eine fragwürdige Designentscheidung; Gleichstellungsvergleiche sind nicht für jeden Typ sinnvoll.String x = (String) myListOfStrings.get(0)
Object
bis auf sie trotzdem zugegriffen wurde. Generics in Java bieten nur implizites Casting, das durch Compiler-Regeln abgesichert ist.Dies ist in einem Fall akzeptabel, in dem Sie eine Entscheidung treffen müssen, die zwei Typen umfasst , und diese Entscheidung in einem Objekt außerhalb dieser Typenhierarchie gekapselt ist. Angenommen, Sie planen, welches Objekt als Nächstes in einer Liste von Objekten verarbeitet wird, die auf die Verarbeitung warten:
Nehmen wir nun an, unsere Geschäftslogik lautet wörtlich "alle Autos haben Vorrang vor Booten und Lastwagen". Wenn Sie
Priority
der Klasse eine Eigenschaft hinzufügen , können Sie diese Geschäftslogik nicht sauber ausdrücken, da dies zu folgendem Ergebnis führt:Das Problem ist, dass Sie zum Verständnis der Prioritätsreihenfolge alle Unterklassen betrachten müssen, oder mit anderen Worten, Sie haben eine Kopplung zu den Unterklassen hinzugefügt.
Sie sollten die Prioritäten natürlich in Konstanten umwandeln und sie für sich in eine Klasse einteilen, um die Planung der Geschäftslogik zusammenzuhalten:
In Wirklichkeit ist der Planungsalgorithmus jedoch etwas, das sich in der Zukunft ändern und letztendlich von mehr als nur dem Typ abhängen kann. Zum Beispiel könnte es heißen: "Lastwagen mit einem Gewicht von über 5000 kg haben Vorrang vor allen anderen Fahrzeugen." Aus diesem Grund gehört der Planungsalgorithmus zu einer eigenen Klasse, und es ist eine gute Idee , den Typ zu untersuchen, um zu bestimmen, welcher zuerst ausgeführt werden soll:
Dies ist die einfachste Art, die Geschäftslogik zu implementieren, und dennoch die flexibelste für zukünftige Änderungen.
quelle
null
aString
oder a enthalten kannString[]
. Wenn 99% der Objekte genau eine Zeichenfolge benötigen, kann das Einkapseln jeder Zeichenfolge in eine separat erstellte Zeichenfolge einenString[]
erheblichen Speicheraufwand verursachen. Die Behandlung des Einzelzeichenfolgenfalls unter Verwendung eines direkten Verweises auf aString
erfordert mehr Code, spart jedoch Speicherplatz und beschleunigt möglicherweise die Arbeit.Die Typprüfung ist ein Werkzeug, das mit Bedacht eingesetzt wird und ein mächtiger Verbündeter sein kann. Verwenden Sie es schlecht und Ihr Code wird anfangen zu riechen.
In unserer Software haben wir als Antwort auf Anfragen Nachrichten über das Netzwerk erhalten. Alle deserialisierten Nachrichten hatten eine gemeinsame Basisklasse
Message
.Die Klassen selbst waren sehr einfach, nur die Nutzdaten als typisierte C # -Eigenschaften und Routinen zum Zuordnen und Zuordnen (Tatsächlich habe ich die meisten Klassen mit t4-Vorlagen aus der XML-Beschreibung des Nachrichtenformats generiert).
Code wäre so etwas wie:
Zugegeben, man könnte argumentieren, dass die Nachrichtenarchitektur besser entworfen werden könnte, aber sie wurde vor langer Zeit entworfen und nicht für C #, also ist es das, was es ist. Hier hat die Typprüfung ein echtes Problem für uns nicht allzu schäbig gelöst.
Bemerkenswert ist, dass C # 7.0 eine Mustererkennung erhält (was in vielerlei Hinsicht ein Typentest für Steroide ist), es kann nicht alles schlecht sein ...
quelle
Nehmen Sie einen generischen JSON-Parser. Das Ergebnis einer erfolgreichen Analyse ist ein Array, ein Wörterbuch, eine Zeichenfolge, eine Zahl, ein Boolescher Wert oder ein Nullwert. Es kann einer von denen sein. Und die Elemente eines Arrays oder die Werte in einem Wörterbuch können wieder alle diese Typen sein. Da die Daten von außerhalb Ihres Programms bereitgestellt werden, müssen Sie jedes Ergebnis akzeptieren (das heißt, Sie müssen es akzeptieren, ohne abstürzen zu müssen; Sie können ein Ergebnis ablehnen, das nicht Ihren Erwartungen entspricht).
quelle