Die obige Frage ist ein abstraktes Beispiel für ein häufiges Problem, auf das ich im Legacy-Code stoße, oder genauer gesagt, Probleme, die sich aus früheren Versuchen zur Lösung dieses Problems ergeben.
Ich kann mir mindestens eine .NET Framework-Methode vorstellen, die dieses Problem lösen soll, wie die Enumerable.OfType<T>
Methode. Aber die Tatsache, dass Sie letztendlich den Typ eines Objekts zur Laufzeit abfragen, passt nicht zu mir.
Darüber hinaus fragen jedes Pferd "Bist du ein Einhorn?" Die folgenden Ansätze kommen auch in den Sinn:
- Eine Ausnahme auslösen, wenn versucht wird, die Länge des Horns eines Nicht-Einhorns zu ermitteln
- Rückgabe eines Standard- oder Zauberwerts für die Länge des Horns eines Nicht-Einhorns (erfordert Standardprüfungen in jedem Code, der die Hornstatistik einer Gruppe von Pferden, die alle Nicht-Einhörner sein könnten, überprüfen soll)
- Beseitigen Sie die Vererbung und erstellen Sie ein separates Objekt auf einem Pferd, das Ihnen sagt, ob das Pferd ein Einhorn ist oder nicht (was möglicherweise dasselbe Problem in einer Ebene nach unten drückt).
Ich habe das Gefühl, dass dies am besten mit einem "Nicht-Antworten" beantwortet wird. Aber wie gehen Sie dieses Problem an, und wenn es davon abhängt, in welchem Kontext stehen Sie zu Ihrer Entscheidung?
Ich würde mich auch für Erkenntnisse interessieren, ob dieses Problem im Funktionscode noch vorhanden ist (oder vielleicht nur in Funktionssprachen, die die Wandlungsfähigkeit unterstützen?).
Dies wurde als mögliches Duplikat der folgenden Frage gekennzeichnet: Wie vermeide ich Downcasting?
Die Beantwortung dieser Frage setzt voraus, dass man im Besitz eines ist, HornMeasurer
an dem alle Hornmessungen vorgenommen werden müssen. Aber das ist eine ziemliche Zumutung für eine Codebasis, die nach dem egalitären Prinzip gebildet wurde, dass jeder frei sein sollte, das Horn eines Pferdes zu messen.
Ohne a HornMeasurer
spiegelt der Ansatz der akzeptierten Antwort den oben aufgeführten ausnahmebasierten Ansatz wider.
Es gab auch einige Verwirrung in den Kommentaren darüber, ob Pferde und Einhörner beide Pferde sind oder ob ein Einhorn eine magische Unterart des Pferdes ist. Beide Möglichkeiten sollten in Betracht gezogen werden - vielleicht ist eine der anderen vorzuziehen?
quelle
Antworten:
Angenommen, Sie möchten eine
Unicorn
als eine besondere Art von behandelnHorse
, dann gibt es grundsätzlich zwei Möglichkeiten, wie Sie sie modellieren können. Der traditionellere Weg ist die Unterklassenbeziehung. Sie können vermeiden, den Typ und das Downcasting zu überprüfen, indem Sie einfach Ihren Code überarbeiten, um die Listen immer in den Kontexten zu trennen, in denen es darauf ankommt, und sie nur in den Kontexten zu kombinieren, in denen Sie sich nie umUnicorn
Merkmale kümmern . Mit anderen Worten, Sie arrangieren es so, dass Sie nie in die Situation geraten, in der Sie Einhörner aus einer Herde von Pferden extrahieren müssen. Dies scheint zunächst schwierig zu sein, ist jedoch in 99,99% der Fälle möglich und führt in der Regel dazu, dass Ihr Code viel sauberer wird.Die andere Möglichkeit, ein Einhorn zu modellieren, besteht darin, allen Pferden eine optionale Hornlänge zuzuweisen. Dann können Sie testen, ob es sich um ein Einhorn handelt, indem Sie prüfen, ob es eine Hornlänge hat, und die durchschnittliche Hornlänge aller Einhörner von (in Scala) ermitteln:
Diese Methode hat den Vorteil, dass sie mit einer einzelnen Klasse einfacher ist, aber den Nachteil, dass sie viel weniger erweiterbar ist und eine Art Umweg über die Überprüfung auf "Einhörnigkeit" bietet. Der Trick bei dieser Lösung besteht darin, zu erkennen, wann Sie beginnen, sie häufig zu erweitern, und zu einer flexibleren Architektur überzugehen. Diese Art von Lösung ist in funktionalen Sprachen weitaus beliebter, in denen Sie einfache und leistungsstarke Funktionen haben, mit denen Sie
flatMap
dieNone
Elemente leicht herausfiltern können.quelle
Horse
eineIsUnicorn
Eigenschaft und eine ArtUnicornStuff
Eigenschaft mit der Hornlänge darauf hatte (beim Skalieren für den in Ihrer Frage erwähnten Fahrer / Glitzer).Sie haben so ziemlich alle Optionen abgedeckt. Wenn Sie ein Verhalten haben, das von einem bestimmten Subtyp abhängt und mit anderen Typen gemischt ist, muss Ihr Code diesen Subtyp kennen. das ist einfach logisch.
Persönlich würde ich einfach mitgehen
horses.OfType<Unicorn>().Average(u => u.HornLength)
. Es drückt die Absicht des Codes sehr deutlich aus, was oft das Wichtigste ist, da es später von jemandem gepflegt werden muss.quelle
Unicorn
sowieso nur s enthält (für den Datensatz, den Sie weglassen könntenreturn
).Es ist nichts falsch in .NET mit:
Die Verwendung des Linq-Äquivalents ist ebenfalls in Ordnung:
Basierend auf der Frage, die Sie im Titel gestellt haben, ist dies der Code, den ich erwarten würde, zu finden. Wenn die Frage so etwas wie "Was ist der Durchschnitt von Tieren mit Hörnern" stellen würde, wäre das anders:
Beachten Sie, dass bei Verwendung von Linq
Average
(undMin
undMax
) eine Ausnahme ausgelöst wird, wenn die Aufzählung leer und der Typ T nicht nullwertfähig ist. Das liegt daran, dass der Durchschnitt wirklich nicht definiert ist (0/0). Also wirklich brauchst du so etwas:Bearbeiten
Ich denke, dies muss hinzugefügt werden. Einer der Gründe, warum eine solche Frage bei objektorientierten Programmierern nicht gut ankommt, ist die Annahme, dass wir Klassen und Objekte zum Modellieren einer Datenstruktur verwenden. Die ursprüngliche objektorientierte Idee für Smalltalk war, Ihr Programm aus Modulen zu strukturieren, die als Objekte instanziiert wurden und Dienste für Sie ausführten, als Sie ihnen eine Nachricht schickten. Die Tatsache, dass wir auch Klassen und Objekte zum Modellieren einer Datenstruktur verwenden können, ist ein (nützlicher) Nebeneffekt, aber sie sind zwei verschiedene Dinge. Ich glaube nicht einmal , diese sollten die objektorientierte Programmierung in Betracht gezogen werden, da Sie könnten das gleiche mit einem tun
struct
, aber es wäre einfach nicht so schön sein.Wenn Sie objektorientierte Programmierung verwenden, um Dienste zu erstellen, die Dinge für Sie tun, wird die Frage, ob es sich bei diesem Dienst tatsächlich um einen anderen Dienst oder um eine konkrete Implementierung handelt, im Allgemeinen aus guten Gründen verpönt. Sie erhielten eine Schnittstelle (in der Regel durch Abhängigkeitsinjektion) und sollten diese Schnittstelle / diesen Vertrag codieren.
Wenn Sie andererseits die Ideen für Klassen, Objekte und Schnittstellen (falsch) verwenden, um eine Datenstruktur oder ein Datenmodell zu erstellen, sehe ich persönlich kein Problem darin, die is-a-Idee in vollem Umfang zu nutzen. Wenn Sie definiert haben, dass Einhörner eine Unterart von Pferden sind und dies in Ihrer Domäne durchaus Sinn macht, fragen Sie unbedingt die Pferde in Ihrer Herde nach den Einhörnern. Schließlich versuchen wir in einem solchen Fall in der Regel, eine domänenspezifische Sprache zu erstellen, um die Lösungen der Probleme, die wir lösen müssen, besser auszudrücken. In diesem Sinne ist an
.OfType<Unicorn>()
etc. nichts auszusetzen .Letztendlich ist es nur eine funktionale Programmierung, eine Sammlung von Elementen zu nehmen und sie nach Typ zu filtern, und keine objektorientierte Programmierung. Zum Glück können Sprachen wie C # beide Paradigmen jetzt problemlos handhaben.
quelle
animal
das ein istUnicorn
; Gießen Sie sie einfach, anstatt sie zu verwendenas
, oder verwenden Sie sie möglicherweise sogar noch besser,as
und suchen Sie dann nach null.Das Problem bei dieser Anweisung ist, dass Sie das Objekt unabhängig von dem verwendeten Mechanismus immer abfragen, um festzustellen, um welchen Typ es sich handelt. Das kann RTTI sein oder es kann eine Union oder eine einfache Datenstruktur sein, nach der Sie fragen
if horn > 0
. Die genauen Einzelheiten ändern sich geringfügig, aber die Absicht ist dieselbe - Sie fragen das Objekt in irgendeiner Weise nach sich selbst, um zu sehen, ob Sie es weiter abfragen sollten.In Anbetracht dessen ist es sinnvoll, die Unterstützung Ihrer Sprache zu verwenden, um dies zu tun. In .NET würden Sie
typeof
zum Beispiel verwenden.Der Grund dafür ist nicht nur, dass Sie Ihre Sprache gut verwenden. Wenn Sie ein Objekt haben, das aussieht wie ein anderes, aber eine kleine Veränderung aufweist, werden Sie mit der Zeit wahrscheinlich weitere Unterschiede feststellen. In Ihrem Beispiel für Einhörner / Pferde sagen Sie vielleicht, dass es nur eine Hornlänge gibt ... aber morgen werden Sie prüfen, ob ein potenzieller Reiter eine Jungfrau ist oder ob der Poop glitzert. (Ein klassisches Beispiel aus der Praxis wären GUI-Widgets, die von einer gemeinsamen Basis abgeleitet sind und bei denen Sie nach Kontrollkästchen und Listboxen unterschiedlich suchen müssen. Die Anzahl der Unterschiede wäre zu groß, um einfach ein einziges Superobjekt zu erstellen, das alle möglichen Datenpermutationen enthält ).
Wenn die Überprüfung des Objekttyps zur Laufzeit nicht funktioniert, können Sie die verschiedenen Objekte von Anfang an aufteilen. Anstatt eine einzelne Herde von Einhörnern / Pferden zu speichern, verfügen Sie über zwei Sammlungen, eine für Pferde und eine für Einhörner . Dies kann sehr gut funktionieren, auch wenn Sie sie in einem speziellen Container speichern (z. B. einer Multimap, bei der der Schlüssel der Objekttyp ist). Aber obwohl wir sie in zwei Gruppen speichern, sind wir gleich wieder beim Abfragen des Objekttyps !)
Ein ausnahmebasierter Ansatz ist sicherlich falsch. Die Verwendung von Ausnahmen als normaler Programmablauf ist ein Codegeruch (wenn Sie eine Herde Einhörner und einen Esel mit einer Muschel am Kopf haben, die sich hineinschleicht), dann würde ich sagen, dass ein ausnahmebasierter Ansatz in Ordnung ist, aber wenn Sie eine Herde Einhörner haben und Pferde, die dann jeweils auf Einhörnigkeit prüfen, sind nicht unerwartet. Ausnahmen sind für außergewöhnliche Umstände keine komplizierte
if
Aussage). In jedem Fall wird der Objekttyp zur Laufzeit nur abgefragt, wenn Ausnahmen für dieses Problem verwendet werden. Nur hier wird die Sprachfunktion missbraucht, um nach Nicht-Einhorn-Objekten zu suchen. Sie können auch aif horn > 0
und zumindest Ihre Sammlung schnell, klar und mit weniger Codezeilen zu verarbeiten und Probleme zu vermeiden, die entstehen, wenn andere Ausnahmen ausgelöst werden (z. B. eine leere Sammlung oder der Versuch, die Muschel dieses Esels zu messen).quelle
if horn > 0
ist es so ziemlich die Art und Weise, wie dieses Problem zuerst gelöst wird. Die Probleme, die normalerweise auftauchen, sind, wenn Sie Fahrer und Glitzer überprüfen möchten, undhorn > 0
sie sind überall in nicht verwandtem Code vergraben (auch der Code leidet unter mysteriösen Fehlern, da nicht geprüft wird , ob die Hupe 0 ist). Außerdem ist es in der Regel am teuersten, ein Pferd nachträglich zu klassifizieren, weshalb ich normalerweise nicht dazu geneigt bin, wenn sie am Ende des Refaktors noch zusammen eingepfercht sind. So wird es sicherlich "wie hässlich sind die Alternativen"Da die Frage mit einem
functional-programming
Tag versehen ist, können wir einen Summentyp verwenden, um die beiden Geschmacksrichtungen von Pferden widerzuspiegeln, und einen Mustervergleich, um zwischen ihnen zu unterscheiden. Zum Beispiel in F #:FP hat gegenüber OOP den Vorteil einer Daten- / Funktionstrennung, die Sie möglicherweise vor dem (ungerechtfertigten?) "Schlechten Gewissen" bewahrt, die Abstraktionsebene zu verletzen, wenn Sie aus einer Liste von Objekten eines Supertyps auf bestimmte Subtypen heruntertragen.
Im Gegensatz zu den in anderen Antworten vorgeschlagenen OO-Lösungen bietet Pattern Matching auch einen einfacheren Erweiterungspunkt, falls
Equine
eines Tages eine andere Horned-Art auftaucht.quelle
Die Kurzform derselben Antwort am Ende erfordert das Lesen eines Buches oder eines Webartikels.
Besuchermuster
Das Problem ist eine Mischung aus Pferden und Einhörnern. (Die Verletzung des Liskov-Substitutionsprinzips ist ein häufiges Problem in älteren Codebasen.)
Fügen Sie dem Pferd und allen Unterklassen eine Methode hinzu
Das Benutzerinterface von Equine sieht in Java / C # ungefähr so aus.
Um Hörner zu messen schreiben wir jetzt ....
Das Besuchermuster wird dafür kritisiert, Refactoring und Wachstum zu erschweren.
Kurze Antwort: Verwenden Sie das Designmuster Besucher, um doppelten Versand zu erhalten.
Siehe auch https://en.wikipedia.org/wiki/Visitor_pattern
Siehe auch http://c2.com/cgi/wiki?VisitorPattern zur Diskussion der Besucher.
siehe auch Design Patterns von Gamma et al.
quelle
Angenommen, in Ihrer Architektur sind Einhörner eine Unterart von Pferden und Sie begegnen Orten, an denen Sie eine Sammlung von Stellen finden, an
Horse
denen sich einige befinden könntenUnicorn
, würde ich persönlich mit der ersten Methode (.OfType<Unicorn>()...
) arbeiten, da dies die einfachste Art ist, Ihre Absicht auszudrücken . Für jeden, der später vorbeikommt (einschließlich sich selbst in 3 Monaten), ist sofort klar, was Sie mit diesem Code erreichen wollen: Wählen Sie die Einhörner unter den Pferden aus.Die anderen Methoden, die Sie aufgelistet haben, sind nur eine andere Möglichkeit, die Frage "Sind Sie ein Einhorn?" Zu stellen. Wenn Sie beispielsweise eine ausnahmebasierte Methode zum Messen von Hörnern verwenden, könnte der Code so aussehen:
Jetzt wird die Ausnahme zum Indikator dafür, dass etwas kein Einhorn ist. Und jetzt ist dies keine Ausnahmesituation mehr, sondern Teil des normalen Programmablaufs. Und die Verwendung einer Ausnahme anstelle einer
if
scheint noch schmutziger zu sein, als nur die Typprüfung durchzuführen.Nehmen wir an, Sie gehen den Weg der magischen Werte, um Hörner bei Pferden zu überprüfen. So, jetzt sehen deine Klassen ungefähr so aus:
Jetzt muss Ihre
Horse
Klasse über dieUnicorn
Klasse Bescheid wissen und zusätzliche Methoden haben, um mit Dingen umzugehen, die ihr egal sind. Stellen Sie sich jetzt vor, Sie haben auchPegasus
s undZebra
s, die von erbenHorse
. NunHorse
muss einFly
Verfahren sowieMeasureWings
,CountStripes
etc. Und dann dieUnicorn
bekommt Klasse auch diese Methoden. Jetzt müssen alle Ihre Klassen voneinander wissen und Sie haben die Klassen mit einer Reihe von Methoden verschmutzt, die nicht vorhanden sein sollten, nur um zu vermeiden, das Typensystem zu fragen: "Ist das ein Einhorn?"Wie wäre
Horse
es also, wenn Sie etwas zu s hinzufügen, um zu sagen, ob etwas a ist,Unicorn
und alle Hornmessungen durchführen? Nun müssen Sie prüfen, ob dieses Objekt existiert, um festzustellen, ob es sich um ein Einhorn handelt (das nur einen Scheck durch einen anderen ersetzt). Es trübt auch das Wasser ein wenig, dass Sie jetzt vielleicht eine habenList<Horse> unicorns
das hält wirklich alle Einhörner, aber das Typsystem und der Debugger können Ihnen das nicht leicht sagen. "Aber ich weiß, es sind alles Einhörner", sagst du, "der Name sagt es sogar." Was ist, wenn etwas einen schlechten Namen hat? Oder sagen Sie, Sie haben etwas mit der Annahme geschrieben, dass es wirklich nur Einhörner sind, aber dann haben sich die Anforderungen geändert und jetzt könnte auch Pegasi eingemischt werden? (Weil nichts dergleichen jemals passiert, insbesondere bei Legacy-Software / Sarkasmus.) Jetzt wird das Typensystem Ihre Pegasi gerne bei Ihren Einhörnern einsetzen. Wenn Ihre Variable alsList<Unicorn>
Compiler (oder als Laufzeitumgebung) deklariert worden wäre, würde dies passen, wenn Sie versuchen würden, Pegasi oder Pferde einzumischen.Schließlich sind alle diese Methoden nur ein Ersatz für die Typprüfung. Persönlich würde ich das Rad hier lieber nicht neu erfinden und hoffen, dass mein Code genauso gut funktioniert wie etwas, das eingebaut ist und von Tausenden anderen Programmierern tausendfach getestet wurde.
Letztendlich muss der Code für Sie verständlich sein . Der Computer wird es herausfinden, unabhängig davon, wie Sie es schreiben. Sie sind derjenige, der es debuggen und darüber nachdenken muss. Treffen Sie die Wahl, die Ihnen die Arbeit erleichtert. Wenn aus irgendeinem Grund eine dieser anderen Methoden einen Vorteil für Sie darstellt, der klareren Code an den Stellen überwiegt, an denen er auftauchen würde, greifen Sie zu. Aber das hängt von Ihrer Codebasis ab.
quelle
if(horse.IsUnicorn) horse.MeasureHorn();
Ausnahmen abgefangen würden und nicht - sie würden entweder ausgelöst, wenn!horse.IsUnicorn
Sie sich in einem Einhorn-Messkontext befinden oder sichMeasureHorn
auf einem Nicht-Einhorn befinden. Auf diese Weise wird die Ausnahme, wenn Sie keine Fehler maskieren, vollständig in die Luft gesprengt und ist ein Zeichen dafür, dass etwas behoben werden muss. Natürlich ist es nur für bestimmte Szenarien geeignet, aber es ist eine Implementierung, die keine Ausnahmebedingung verwendet, um einen Ausführungspfad zu bestimmen.Nun, es hört sich so an, als ob Ihre semantische Domäne eine IS-A-Beziehung hat, aber Sie sind ein bisschen vorsichtig, Subtypen / Vererbung zu verwenden, um dies zu modellieren - insbesondere aufgrund der Laufzeit-Typreflexion. Ich glaube jedoch, dass Sie Angst vor dem Falschen haben - Subtypisierung birgt zwar Gefahren, aber die Tatsache, dass Sie ein Objekt zur Laufzeit abfragen, ist nicht das Problem. Du wirst sehen, was ich meine.
Die objektorientierte Programmierung hat sich ziemlich stark auf den Begriff der IS-A-Beziehungen gestützt, wohl zu stark darauf, was zu zwei berühmten kritischen Konzepten führte:
Aber ich denke, es gibt eine andere, funktionalere, auf der Programmierung basierende Möglichkeit, IS-A-Beziehungen zu betrachten, die diese Schwierigkeiten möglicherweise nicht haben. Zuerst wollen wir Pferde und Einhörner in unserem Programm modellieren, also werden wir einen
Horse
und einenUnicorn
Typ haben. Was sind die Werte dieser Typen? Nun, ich würde das sagen:Das mag offensichtlich klingen, aber ich denke, einer der Wege, wie Menschen in Probleme wie das Kreis-Ellipsen-Problem geraten, besteht darin, diese Punkte nicht sorgfältig genug zu beachten. Jeder Kreis ist eine Ellipse, aber das bedeutet nicht, dass jede schematisierte Beschreibung eines Kreises automatisch eine schematisierte Beschreibung einer Ellipse nach einem anderen Schema ist. Mit anderen Worten, nur weil ein Kreis eine Ellipse ist, heißt das nicht, dass a sozusagen eine
Circle
istEllipse
. Aber es bedeutet, dass:Circle
(schematisierte Kreisbeschreibung) in eineEllipse
(andere Art von Beschreibung) umwandelt , die dieselben Kreise beschreibt.Ellipse
und, wenn sie einen Kreis beschreibt, die entsprechende zurückgibtCircle
.In Bezug auf die funktionale Programmierung muss Ihr
Unicorn
Typ also nicht unbedingt ein Subtyp sein, sondernHorse
Sie benötigen nur Operationen wie die folgenden:Und
toUnicorn
muss eine Umkehrung von seintoHorse
:Haskells
Maybe
Typ ist der Typ, den andere Sprachen als "Option" bezeichnen. Beispielsweise ist der Java 8-Optional<Unicorn>
Typ entweder einUnicorn
oder nichts. Beachten Sie, dass zwei Ihrer Alternativen - Auslösen einer Ausnahme oder Zurückgeben eines "Standard- oder Zauberwerts" - Optionstypen sehr ähnlich sind.Im Grunde genommen habe ich hier das Konzept der IS-A-Beziehung in Bezug auf Typen und Funktionen rekonstruiert, ohne Subtypen oder Vererbung zu verwenden. Was ich davon wegnehmen würde, ist:
Horse
Typ haben.Horse
Typ muss genügend Informationen codieren, um eindeutig zu bestimmen, ob ein Wert ein Einhorn beschreibt.Horse
Typs müssen diese Informationen offenlegen, damit Clients des Typs beobachten können, ob eine gegebeneHorse
ein Einhorn ist.Horse
Typs müssen diese letzteren Operationen zur Laufzeit verwenden, um zwischen Einhörnern und Pferden zu unterscheiden.Das ist also im Grunde genommen ein "frage jeden,
Horse
ob es ein Einhorn ist" -Modell. Sie sind vorsichtig mit diesem Modell, aber ich denke falsch. Wenn ich Ihnen eine Liste vonHorse
s gebe , ist alles, was der Typ garantiert, dass die Dinge, die in der Liste beschrieben werden, Pferde sind - Sie müssen also zwangsläufig zur Laufzeit etwas tun, um festzustellen, welche von ihnen Einhörner sind. Ich glaube, daran führt kein Weg vorbei - Sie müssen Operationen implementieren, die dies für Sie erledigen.In der objektorientierten Programmierung ist dies wie folgt bekannt:
Horse
Typ;Unicorn
als UntertypHorse
;Horse
eine istUnicorn
.Dies hat eine große Schwäche, wenn man es aus dem Blickwinkel "Ding vs. Beschreibung" betrachtet, den ich oben vorgestellt habe:
Horse
Instanz haben, die ein Einhorn beschreibt, aber keineUnicorn
Instanz ist?Zurück zum Anfang, dies ist meiner Meinung nach der wirklich beängstigende Teil der Verwendung von Subtyping und Downcasts zur Modellierung dieser IS-A-Beziehung - nicht die Tatsache, dass Sie eine Laufzeitprüfung durchführen müssen. Die Typografie ein wenig zu missbrauchen und zu fragen,
Horse
ob es sich um eineUnicorn
Instanz handelt, ist nicht gleichbedeutend mit der Frage,Horse
ob es sich um ein Einhorn handelt (ob es sich um eineHorse
Beschreibung eines Pferdes handelt, das auch ein Einhorn ist). Es sei denn, Ihr Programm hat große Anstrengungen unternommen, um den Code zu kapseln, der erstellt wird,Horses
sodassHorse
dieUnicorn
Klasse jedes Mal instanziiert wird, wenn ein Client versucht, ein Einhorn zu erstellen. Nach meiner Erfahrung tun Programmierer Dinge selten so sorgfältig.Ich würde also bei dem Ansatz vorgehen, bei dem es eine explizite Operation ohne Downcast gibt, die
Horse
s inUnicorn
s konvertiert . Dies kann entweder eine Methode desHorse
Typs sein:... oder es könnte ein externes Objekt sein (Ihr "separates Objekt auf einem Pferd, das Ihnen sagt, ob das Pferd ein Einhorn ist oder nicht"):
Die Wahl zwischen diesen Optionen hängt davon ab, wie Ihr Programm organisiert ist - in beiden Fällen haben Sie das Äquivalent zu meiner
Horse -> Maybe Unicorn
Operation von oben, Sie verpacken sie nur auf unterschiedliche Weise (was zugegebenermaßen Welligkeitseffekte auf die Operationen hat, die derHorse
Typ benötigt seinen Kunden aussetzen).quelle
Der Kommentar von OP in einer anderen Antwort hat die Frage geklärt, dachte ich
So formuliert, glaube ich, brauchen wir mehr Informationen. Die Antwort hängt wahrscheinlich von einer Reihe von Dingen ab:
herd.averageHornLength()
scheint unserem konzeptuellen Modell zu entsprechen.Im Allgemeinen würde ich hier jedoch nicht einmal über Vererbung und Subtypen nachdenken. Sie haben eine Liste von Objekten. Einige dieser Objekte können als Einhörner identifiziert werden, vielleicht weil sie eine
hornLength()
Methode haben. Filtern Sie die Liste anhand dieser einhornspezifischen Eigenschaft. Jetzt wurde das Problem auf die Mittelung der Hornlänge einer Liste von Einhörnern reduziert.OP, lass es mich wissen, wenn ich immer noch Missverständnisse habe ...
quelle
HerdMember
wir) festlegen, das entweder mit einem Pferd oder mit einem Einhorn initialisiert wird (um Pferd und Einhorn von der Notwendigkeit einer Untertypbeziehung zu befreien) ).HerdMember
ist dann frei zu implementieren,isUnicorn()
aber es sieht so aus, und die Filterlösung, die ich vorschlage, folgt.Eine Methode GetUnicorns (), die eine IEnumerable zurückgibt, scheint mir die eleganteste, flexibelste und universellste Lösung zu sein. Auf diese Weise können Sie mit jeder (Kombination von) Merkmalen umgehen, die bestimmen, ob ein Pferd als Einhorn gilt, und nicht nur mit dem Klassentyp oder dem Wert einer bestimmten Eigenschaft.
quelle
horses.ofType<Unicorn>...
Konstrukte. EineGetUnicorns
Funktion wäre ein Einzeiler, aber sie wäre aus Sicht des Anrufers widerstandsfähiger gegen Änderungen der Beziehung zwischen Pferd und Einhorn.IEnumerable<Horse>
, obwohl sich Ihre Einhornkriterien an einer Stelle befinden, ist es gekapselt, sodass Ihre Anrufer Vermutungen anstellen müssen, warum sie Einhörner benötigen. Ich bekomme es morgen, wenn ich das Gleiche tue. Außerdem müssen Sie einen Standardwert für eine Hupe auf demHorse
. WennUnicorn
es sich um einen eigenen Typ handelt, müssen Sie einen neuen Typ erstellen und Typzuordnungen pflegen, was zu einem Mehraufwand führen kann.