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 einen Unicorn
Typ 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 Circle
ist 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
Ellipse
und, wenn sie einen Kreis beschreibt, die entsprechende zurückgibt Circle
.
In Bezug auf die funktionale Programmierung muss Ihr Unicorn
Typ also nicht unbedingt ein Subtyp sein, sondern Horse
Sie 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 toUnicorn
muss eine Umkehrung von sein toHorse
:
toUnicorn (toHorse x) = Just x
Haskells Maybe
Typ ist der Typ, den andere Sprachen als "Option" bezeichnen. Beispielsweise ist der Java 8- Optional<Unicorn>
Typ entweder ein Unicorn
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:
- Ihr Modell muss einen
Horse
Typ haben.
- Der
Horse
Typ muss genügend Informationen codieren, um eindeutig zu bestimmen, ob ein Wert ein Einhorn beschreibt.
- Einige Operationen des
Horse
Typs müssen diese Informationen offenlegen, damit Clients des Typs beobachten können, ob eine gegebene Horse
ein Einhorn ist.
- Die Clients des
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 von Horse
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:
- Habe einen
Horse
Typ;
- Habe
Unicorn
als Untertyp Horse
;
- Verwenden Sie die Laufzeit-Typreflexion als die auf den Client zugreifbare Operation, die erkennt, ob eine gegebene
Horse
eine 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
Horse
Instanz haben, die ein Einhorn beschreibt, aber keine Unicorn
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 eine Unicorn
Instanz handelt, ist nicht gleichbedeutend mit der Frage, Horse
ob es sich um ein Einhorn handelt (ob es sich um eine Horse
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
sodass Horse
die Unicorn
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 in Unicorn
s konvertiert . Dies kann entweder eine Methode des Horse
Typs 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 Unicorn
Operation von oben, Sie verpacken sie nur auf unterschiedliche Weise (was zugegebenermaßen Welligkeitseffekte auf die Operationen hat, die der Horse
Typ benötigt seinen Kunden aussetzen).