継承を使うべき場面

継承とは、既存のクラス(親クラス)を元にして 新しいクラス(子クラス)を定義することです。 子クラス側では、親クラスの機能を一部上書きしたり 新しい機能を追加したりすることが出来ます。 継承は強力ですが、使い方を間違えると 簡単に変な設計をすることが出来てしまいますので まず、継承を使うべき場面について考えてみます。 継承を通して、子クラスは親クラスの機能を全て引き継ぎます。 つまり、子クラスは親クラスの一種と言える訳です。 ここがポイントで、 「子クラスは親クラスである」 といえる関係が成り立つ場合にのみ継承を使うべきだと 一般的には言われています。 例えば車を考えると 「ワゴン車は車である」 「乗用車は車である」 ということは出来るので、 車クラスを継承してワゴン車クラスや乗用車クラスを作るのは 問題ありません。 しかし、 「タイヤは車である」 というのは若干無理があり タイヤクラスを継承して車クラスを作るのは良くありません。 この「〜は〜である」という関係のことを オブジェクト指向の用語では「is-a関係」と呼びます。 継承の原則を一言で述べるとすれば ○「is-a関係」がある場合にのみ継承させるべき となります。 また「〜は〜を持つ」という関係のことを 「has-a関係」と呼びます。 上の例では「車はタイヤを持つ」と言えるため 車とタイヤは「has-a関係」にあります。 この「has-a関係」が成り立つような場合には 継承ではなく、集約(aggregation)または 複合(composition)と呼ばれるテクニックを用いると うまくクラスをデザインすることが出来ます。 これについては、また後ほど説明します

継承

F#の継承はJavaやC#等の言語と同じく 単一の親クラスからの継承(とインタフェース継承)のみをサポートしています。 これはおそらく、複数の親クラスからの継承(多重継承)には ダイヤモンド型の継承など様々な問題があるためだと思われます。 クラスの継承は、classキーワードに続けて inheritキーワード、基本クラスと指定します。 まずは、親クラスを作ります。
準備:親クラスの定義
type Mouse = class
    val name : string
    new (n) = {name = n}
    end;;
let hatuka = new Mouse("Hatuka nezumi");;
今回は例としてねずみクラスを作ってみます。 上記の通り、名前を持つだけの単純なクラスです。 次に、これを継承したポケットを持つねずみクラスを作ります。
クラスの継承
type PocketMouse = class inherit Mouse
    val pocket : string
    new (namae,contents) = {inherit Mouse(namae);pocket = contents;}
    end;;
let dora = new PocketMouse("doraemon","4jigen pocket");;
let pika = new PocketMouse("pikachu","denki bukuro");;
mouseクラスを継承してpocketmouseクラスを作成しています。 pocketmouseクラスは、mouseクラスの持つメンバを全て引き継ぐため 特に宣言はされていませんがnameフィールドを持ちます。 継承を行っているのはclass inherit mouseの部分で これにより、mouseクラスを継承すると宣言しています。 また、コンストラクタの初期化の箇所では 親クラスのコンストラクタを呼び出しており、 このような呼び出しをする際にもinheritキーワードを用います。 クラスを作る際には、親クラスと子クラスの コンストラクタの呼び出し順序を知っておくことは有用です。 次のプログラムでは、 コンストラクタの呼び出し順序を確認してみました。
コンストラクタの呼び出し順序
type Mouse = class
    val name : string
    new (n) = {name = n} then System.Console.WriteLine("base class constructor called")
    end;;
type PocketMouse = class inherit Mouse
    val pocket : string
    new (namae,contents) = {inherit Mouse(namae);pocket = contents;}
    then System.Console.WriteLine("sub class constructor called");
    end;;
let dora = new PocketMouse("doraemon","4jigen pocket");;
次の通り、コンストラクタは基本クラスのものから呼び出されます。
実行結果
base class constructor called
sub class constructor called
続行するには何かキーを押してください . . .
また、子クラスのコンストラクタの初期化部分で 親クラスのコンストラクタを明示的に呼び出さない場合は ()を引数としたコンストラクタが呼ばれます。
初期化を省略した場合のコンストラクタの動作
type Mouse = class
    val name : string
    new () = {name = "nezumi"} then System.Console.WriteLine("base class constructor called")
    end;;
type PocketMouse = class inherit Mouse
    val pocket : string
    new (contents) = {pocket = contents;}
    then System.Console.WriteLine("sub class constructor called");
    end;;
let dora = new PocketMouse("4jigen pocket");;
この結果も先ほど同じものになります
実行結果
base class constructor called
sub class constructor called
続行するには何かキーを押してください . . .
次の通り、()を引数に受け付けるコンストラクタがない場合は コンパイルエラーとなります
()を受け付けるコンストラクタがないためエラー
type Mouse = class
    val name : string
    new (n) = {name = n} then System.Console.WriteLine("base class constructor called")
    end;;
type PocketMouse = class inherit Mouse
    val pocket : string
    new (contents) = {pocket = contents;}
    then System.Console.WriteLine("sub class constructor called");
    end;;
let dora = new PocketMouse("4jigen pocket");;
最後に、暗黙的なコンストラクタを用いて 同様のプログラムを記述すると次のようになります。
暗黙的なコンストラクタを利用したケース
type Mouse(n) = class
    member x.name = n : string
    end;;
type PocketMouse(n,contents) = class inherit Mouse(n)
    member x.pocket = contents : string
    end;;
let dora = new PocketMouse("doraemon","4jigen pocket");;
この場合、親クラスのコンストラクタ呼び出し時の 引数は、inheritキーワードで親クラスを宣言する際に 一緒に指定します。