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 Horseund einen UnicornTyp haben. Was sind die Werte dieser Typen? Nun, ich würde das sagen:
- Die Werte dieser Typen sind Darstellungen oder Beschreibungen von Pferden bzw. Einhörnern.
- Es handelt sich um schematisierte Darstellungen oder Beschreibungen - sie sind nicht frei formuliert, sondern nach sehr strengen Regeln aufgebaut.
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 Circleist Ellipse. Aber es bedeutet, dass:
- Es gibt eine Gesamtfunktion , die eine beliebige
Circle(schematisierte Kreisbeschreibung) in eine Ellipse(andere Art von Beschreibung) umwandelt , die dieselben Kreise beschreibt.
- Es gibt eine Teilfunktion , die eine nimmt
Ellipseund, wenn sie einen Kreis beschreibt, die entsprechende zurückgibt Circle.
In Bezug auf die funktionale Programmierung muss Ihr UnicornTyp also nicht unbedingt ein Subtyp sein, sondern HorseSie benötigen nur Operationen wie die folgenden:
-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse
-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn
Und toUnicornmuss eine Umkehrung von sein toHorse:
toUnicorn (toHorse x) = Just x
Haskells MaybeTyp ist der Typ, den andere Sprachen als "Option" bezeichnen. Beispielsweise ist der Java 8- Optional<Unicorn>Typ entweder ein Unicornoder 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:
- Ihr Modell muss einen
HorseTyp haben.
- Der
HorseTyp muss genügend Informationen codieren, um eindeutig zu bestimmen, ob ein Wert ein Einhorn beschreibt.
- Einige Operationen des
HorseTyps müssen diese Informationen offenlegen, damit Clients des Typs beobachten können, ob eine gegebene Horseein Einhorn ist.
- Die Clients des
HorseTyps 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, Horseob es ein Einhorn ist" -Modell. Sie sind vorsichtig mit diesem Modell, aber ich denke falsch. Wenn ich Ihnen eine Liste von Horses 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:
- Habe einen
HorseTyp;
- Habe
Unicornals Untertyp Horse;
- Verwenden Sie die Laufzeit-Typreflexion als die auf den Client zugreifbare Operation, die erkennt, ob eine gegebene
Horseeine ist Unicorn.
Dies hat eine große Schwäche, wenn man es aus dem Blickwinkel "Ding vs. Beschreibung" betrachtet, den ich oben vorgestellt habe:
- Was ist, wenn Sie eine
HorseInstanz haben, die ein Einhorn beschreibt, aber keine UnicornInstanz 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, Horseob es sich um eine UnicornInstanz handelt, ist nicht gleichbedeutend mit der Frage, Horseob es sich um ein Einhorn handelt (ob es sich um eine HorseBeschreibung 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, Horsessodass Horsedie UnicornKlasse 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 Horses in Unicorns konvertiert . Dies kann entweder eine Methode des HorseTyps sein:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
... 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"):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
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 UnicornOperation von oben, Sie verpacken sie nur auf unterschiedliche Weise (was zugegebenermaßen Welligkeitseffekte auf die Operationen hat, die der HorseTyp benötigt seinen Kunden aussetzen).