■ 應該使用繼承的場合
繼承是指以現有的類(父類)爲基礎,定義新類(子類)。子類繼承父類的所有功能,可以對父類的功能進行改寫,可以追加新的功能。繼承的功用是十分強大的,但是如果使用不得當的話,會使程序設計變得非常奇怪,所以首先讓我們來看應該使用繼承的正確場合。
通過繼承,子類繼承了父類的全部功能,也就是說,子類可以說是父類的一種,這裏是重點,一般說來只有當“子類就是父類的一種”這種關係成立的時候,才應該使用繼承。
拿汽車來舉例說明,“公共汽車是汽車的一種“,“小轎車是汽車的一種“,這種說法是正確的,所以公共汽車類或小轎車類繼承汽車類是不會有什麼問題的。但是,“輪胎是汽車的一種“這種說法是錯誤的,所以輪胎類繼承汽車類是不正確的。
這種“A是B的一種”的關係在面向對象的專門術語種叫做“is-a關係”,談到繼承的原則時,只有“is-a關係”成立時才應該進行繼承。同時,“A具有B"這種關係叫做"has-a關係"。上例中,因爲可以說“汽車具有輪胎”,所以汽車和輪胎的關係是"has-a關係",當這種"has-a關係"成立時不應該使用繼承,而應該使用聚合或複合的技術,這樣才能很好地進行程序設計。關於聚合與複合,在後文詳述。
■ 繼承
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關鍵字聲明父類時一起指定的。