セマンティックドメインにはIS-A関係があるように見えますが、サブタイプ/継承を使用してこれをモデル化することには少し注意が必要です(特にランタイムタイプの反映のため)。しかし、あなたは間違ったことを怖がっていると思います。サブタイプには確かに危険が伴いますが、実行時にオブジェクトを照会しているという事実は問題ではありません。意味がわかります。
オブジェクト指向プログラミングはIS-A関係の概念に非常に強く依存しており、おそらく2つの有名な重要な概念につながっていると思われます。
しかし、IS-Aの関係を見るための、より機能的なプログラミングベースの別の方法があり、おそらくこれらの困難はないと思います。まず、プログラムで馬とユニコーンをモデル化するため、a Horse
とUnicorn
typeを作成します。これらのタイプの値は何ですか?まあ、私はこれを言うだろう:
- これらのタイプの値は、それぞれ馬とユニコーンの表現または説明です。
- これらは、スキーマ化された表現または説明です。自由形式ではなく、非常に厳しいルールに従って構築されています。
それは当たり前のように聞こえるかもしれませんが、人々が円楕円問題のような問題に取り組む方法の1つは、これらの点を十分に気にしないことです。すべての円は楕円ですが、それは、円のすべての図式化された説明が、異なるスキーマに従って楕円の図式化された説明になることを意味しません。つまり、円が楕円だからといって、a Circle
がであるという意味ではありませんEllipse
。しかし、それは次のことを意味します:
- すべての(スキーム化された円の説明)を、同じ円を説明する(異なるタイプの説明)に変換する合計関数があります。
Circle
Ellipse
- を取り、円を記述する場合、対応するを返す部分関数があり
Ellipse
ますCircle
。
したがって、関数型プログラミングの用語では、Unicorn
型はまったくサブタイプである必要はなくHorse
、次のような操作が必要です。
-- 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
そして、以下のtoUnicorn
正反対である必要がありますtoHorse
。
toUnicorn (toHorse x) = Just x
HaskellのMaybe
タイプは、他の言語で「オプション」タイプと呼ばれるものです。たとえば、Java 8 Optional<Unicorn>
タイプは、an Unicorn
またはnoneです。2つの選択肢(例外をスローするか、「デフォルト値またはマジック値」を返す)は、オプションタイプに非常に似ていることに注意してください。
基本的に、ここでやったことは、サブタイプや継承を使用せずに、タイプと機能の観点からIS-A関係の概念を再構築することです。私がこれから取り上げるのは:
- モデルには型が必要
Horse
です。
Horse
型は、任意の値がユニコーンを記述しているかどうかを明確に決定するのに十分な情報を符号化する必要があります。
Horse
タイプの一部の操作では、その情報を公開して、タイプのクライアントが特定Horse
のユニコーンであるかどうかを確認できるようにする必要があります。
- この
Horse
タイプのクライアントは、実行時にこれらの後者の操作を使用して、ユニコーンと馬を区別する必要があります。
したがって、これは基本的に「Horse
ユニコーンかどうかを尋ねる」モデルです。あなたはそのモデルに警戒していますが、私は間違っていると思います。Horse
sのリストを提供する場合、タイプが保証するのは、リスト内のアイテムが記述するものが馬であるということだけです。したがって、必然的に、それらのどれがユニコーンであるかを判断するために実行時に何かをする必要があります。したがって、それを回避することはできません。それを実現する操作を実装する必要があると思います。
オブジェクト指向プログラミングでは、これを行うためのよく知られた方法は次のとおりです。
Horse
タイプを持っている;
- 持っている
Unicorn
のサブタイプとしてHorse
、
- ランタイムタイプリフレクションを、特定の
Horse
がであるかどうかを識別するクライアントアクセス可能な操作として使用しますUnicorn
。
上記に示した「ものと説明」の角度から見ると、これには大きな弱点があります。
- あなたは何を持っている場合は
Horse
ユニコーンを記述していないが、インスタンスUnicorn
のインスタンスを?
初めに戻ると、これは、このIS-A関係のモデリングにサブタイピングとダウンキャストを使用することについて本当に恐ろしいことだと思います。ランタイムチェックを行う必要はありません。タイポグラフィを少し乱用し、Horse
それがUnicorn
インスタンスであるHorse
かどうかを尋ねることは、それがユニコーンであるかどうかを尋ねることと同義ではありません(ユニコーンHorse
でもある馬の説明であるかどうか)。Horses
クライアントがHorse
ユニコーンを記述するを構築しようとするたびにUnicorn
クラスがインスタンス化されるように、構築するコードをカプセル化するためにプログラムが非常に長い時間をかけていない限り、そうではありません。私の経験では、プログラマがこれを慎重に行うことはめったにありません。
したがって、Horse
sをUnicorn
sに変換するダウンキャスト以外の明示的な操作があるアプローチを採用します。これは、次のHorse
タイプのメソッドのいずれかです。
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
...または、外部オブジェクト(「馬がユニコーンであるかどうかを通知する馬上の別個のオブジェクト」)の場合もあります。
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
これらのどちらを選択するかは、プログラムの編成方法に依存します。どちらの場合も、Horse -> Maybe Unicorn
上記の操作と同等であり、異なる方法でパッケージ化するだけです(Horse
タイプに必要な操作に波及効果があることは明らかです)クライアントに公開します)。