F# 程式設計/繼承
| F#:繼承 |
許多面向物件的語言在 .NET BCL 中廣泛使用 **繼承** 來構建類層次結構。
簡單來說,子類是從已經定義的類派生的類。子類除了新增自己的成員外,還從基類繼承其成員。子類使用 `inherit` 關鍵字定義,如下所示
type Person(name) =
member x.Name = name
member x.Greet() = printfn "Hi, I'm %s" x.Name
type Student(name, studentID : int) =
inherit Person(name)
let mutable _GPA = 0.0
member x.StudentID = studentID
member x.GPA
with get() = _GPA
and set value = _GPA <- value
type Worker(name, employer : string) =
inherit Person(name)
let mutable _salary = 0.0
member x.Salary
with get() = _salary
and set value = _salary <- value
member x.Employer = employer
我們的簡單類層次結構如下所示
System.Object (* All classes descend from *) - Person - Student - Worker
`Student` 和 `Worker` 子類都從 `Person` 基類繼承了 `Name` 和 `Greet` 方法。這可以在 fsi 中演示
> let somePerson, someStudent, someWorker =
new Person("Juliet"), new Student("Monique", 123456), new Worker("Carla", "Awesome Hair Salon");;
val someWorker : Worker
val someStudent : Student
val somePerson : Person
> somePerson.Name, someStudent.Name, someWorker.Name;;
val it : string * string * string = ("Juliet", "Monique", "Carla")
> someStudent.StudentID;;
val it : int = 123456
> someWorker.Employer;;
val it : string = "Awesome Hair Salon"
> someWorker.ToString();; (* ToString method inherited from System.Object *)
val it : string = "FSI_0002+Worker"
.NET 的物件模型支援 *單類繼承*,這意味著子類只能有一個基類。換句話說,無法建立同時從 `Student` 和 `Employee` 派生的類。
有時,您可能希望派生類更改從基類繼承的方法的預設行為。例如,上面 `ToString()` 方法的輸出不是很有用。我們可以使用 `override` 關鍵字透過不同的實現來覆蓋這種行為
type Person(name) =
member x.Name = name
member x.Greet() = printfn "Hi, I'm %s" x.Name
override x.ToString() = x.Name (* The ToString() method is inherited from System.Object *)
我們覆蓋了 `ToString()` 方法的預設實現,使其打印出人的姓名。
F# 中的方法預設不可覆蓋。如果您希望使用者能夠在派生類中覆蓋方法,則必須使用 `abstract` 和 `default` 關鍵字將您的方法宣告為可覆蓋,如下所示
type Person(name) =
member x.Name = name
abstract Greet : unit -> unit
default x.Greet() = printfn "Hi, I'm %s" x.Name
type Quebecois(name) =
inherit Person(name)
override x.Greet() = printfn "Bonjour, je m'appelle %s, eh." x.Name
我們的類 `Person` 提供了一個 `Greet` 方法,可以在派生類中覆蓋。以下是在 fsi 中使用這兩個類的示例
> let terrance, phillip = new Person("Terrance"), new Quebecois("Phillip");;
val terrance : Person
val phillip : Quebecois
> terrance.Greet();;
Hi, I'm Terrance
val it : unit = ()
> phillip.Greet();;
Bonjour, je m'appelle Phillip, eh.
抽象類是提供物件不完整實現的類,要求程式設計師建立抽象類的子類來完成其餘的實現。例如,考慮以下情況
[<AbstractClass>]
type Shape(position : Point) =
member x.Position = position
override x.ToString() =
sprintf "position = {%i, %i}, area = %f" position.X position.Y (x.Area())
abstract member Draw : unit -> unit
abstract member Area : unit -> float
您首先會注意到 `AbstractClass` 屬性,它告訴編譯器我們的類包含一些抽象成員。此外,您會注意到兩個抽象成員,`Draw` 和 `Area` 沒有實現,只有型別簽名。
我們無法建立 `Shape` 的例項,因為該類尚未完全實現。相反,我們必須從 `Shape` 派生並用具體實現覆蓋 `Draw` 和 `Area` 方法
type Circle(position : Point, radius : float) =
inherit Shape(position)
member x.Radius = radius
override x.Draw() = printfn "(Circle)"
override x.Area() = Math.PI * radius * radius
type Rectangle(position : Point, width : float, height : float) =
inherit Shape(position)
member x.Width = width
member x.Height = height
override x.Draw() = printfn "(Rectangle)"
override x.Area() = width * height
type Square(position : Point, width : float) =
inherit Shape(position)
member x.Width = width
member x.ToRectangle() = new Rectangle(position, width, width)
override x.Draw() = printfn "(Square)"
override x.Area() = width * width
type Triangle(position : Point, sideA : float, sideB : float, sideC : float) =
inherit Shape(position)
member x.SideA = sideA
member x.SideB = sideB
member x.SideC = sideC
override x.Draw() = printfn "(Triangle)"
override x.Area() =
(* Heron's formula *)
let a, b, c = sideA, sideB, sideC
let s = (a + b + c) / 2.0
Math.Sqrt(s * (s - a) * (s - b) * (s - c) )
現在我們有了 `Shape` 類的幾個不同的實現。我們可以在 fsi 中嘗試使用這些實現
> let position = { X = 0; Y = 0 };;
val position : Point
> let circle, rectangle, square, triangle =
new Circle(position, 5.0),
new Rectangle(position, 2.0, 7.0),
new Square(position, 10.0),
new Triangle(position, 3.0, 4.0, 5.0);;
val triangle : Triangle
val square : Square
val rectangle : Rectangle
val circle : Circle
> circle.ToString();;
val it : string = "Circle, position = {0, 0}, area = 78.539816"
> triangle.ToString();;
val it : string = "Triangle, position = {0, 0}, area = 6.000000"
> square.Width;;
val it : float = 10.0
> square.ToRectangle().ToString();;
val it : string = "Rectangle, position = {0, 0}, area = 100.000000"
> rectangle.Height, rectangle.Width;;
val it : float * float = (7.0, 2.0)
*型別轉換* 是將物件從一種型別更改為另一種型別的操作。這與對映函式不同,因為型別轉換不會返回新物件的例項,而是返回具有不同型別的同一物件的例項。
例如,假設 `B` 是 `A` 的子類。如果我們有一個 `B` 的例項,我們能夠將其轉換為 `A` 的例項。由於 `A` 在類層次結構中向上,我們將其稱為向上轉型。我們使用 `:>` 運算子來執行向上轉型
> let regularString = "Hello world";;
val regularString : string
> let upcastString = "Hello world" :> obj;;
val upcastString : obj
> regularString.ToString();;
val it : string = "Hello world"
> regularString.Length;;
val it : int = 11
> upcastString.ToString();; (* type obj has a .ToString method *)
val it : string = "Hello world"
> upcastString.Length;; (* however, obj does not have Length method *)
upcastString.Length;; (* however, obj does not have Length method *)
-------------^^^^^^^
stdin(24,14): error FS0039: The field, constructor or member 'Length' is not defined.
向上轉型被認為是“安全的”,因為派生類保證擁有與祖先類相同的成員。如果需要,我們可以反過來進行操作:我們可以使用 `:?>` 運算子從祖先類向下轉型為派生類
> let intAsObj = 20 :> obj;;
val intAsObj : obj
> intAsObj, intAsObj.ToString();;
val it : obj * string = (20, "20")
> let intDownCast = intAsObj :?> int;;
val intDownCast : int
> intDownCast, intDownCast.ToString();;
val it : int * string = (20, "20")
> let stringDownCast = intAsObj :?> string;; (* boom! *)
val stringDownCast : string
System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.
at <StartupCode$FSI_0067>.$FSI_0067._main()
stopped due to error
由於 `intAsObj` 儲存一個裝箱為 `obj` 的 `int`,因此我們可以根據需要將其向下轉型為 `int`。但是,我們不能向下轉型為 `string`,因為它是不相容的型別。向下轉型被認為是“不安全的”,因為型別檢查器無法檢測到錯誤,因此向下轉型錯誤總是會導致執行時異常。
open System
type Point = { X : int; Y : int }
[<AbstractClass>]
type Shape() =
override x.ToString() =
sprintf "%s, area = %f" (x.GetType().Name) (x.Area())
abstract member Draw : unit -> unit
abstract member Area : unit -> float
type Circle(radius : float) =
inherit Shape()
member x.Radius = radius
override x.Draw() = printfn "(Circle)"
override x.Area() = Math.PI * radius * radius
type Rectangle(width : float, height : float) =
inherit Shape()
member x.Width = width
member x.Height = height
override x.Draw() = printfn "(Rectangle)"
override x.Area() = width * height
type Square(width : float) =
inherit Shape()
member x.Width = width
member x.ToRectangle() = new Rectangle(width, width)
override x.Draw() = printfn "(Square)"
override x.Area() = width * width
type Triangle(sideA : float, sideB : float, sideC : float) =
inherit Shape()
member x.SideA = sideA
member x.SideB = sideB
member x.SideC = sideC
override x.Draw() = printfn "(Triangle)"
override x.Area() =
(* Heron's formula *)
let a, b, c = sideA, sideB, sideC
let s = (a + b + c) / 2.0
Math.Sqrt(s * (s - a) * (s - b) * (s - c) )
let shapes =
[(new Circle(5.0) :> Shape);
(new Circle(12.0) :> Shape);
(new Square(10.5) :> Shape);
(new Triangle(3.0, 4.0, 5.0) :> Shape);
(new Rectangle(5.0, 2.0) :> Shape)]
(* Notice we have to cast each object as a Shape *)
let main() =
shapes
|> Seq.iter (fun x -> printfn "x.ToString: %s" (x.ToString()) )
main()
該程式具有以下型別
type Point =
{X: int;
Y: int;}
type Shape =
class
abstract member Area : unit -> float
abstract member Draw : unit -> unit
new : unit -> Shape
override ToString : unit -> string
end
type Circle =
class
inherit Shape
new : radius:float -> Circle
override Area : unit -> float
override Draw : unit -> unit
member Radius : float
end
type Rectangle =
class
inherit Shape
new : width:float * height:float -> Rectangle
override Area : unit -> float
override Draw : unit -> unit
member Height : float
member Width : float
end
type Square =
class
inherit Shape
new : width:float -> Square
override Area : unit -> float
override Draw : unit -> unit
member ToRectangle : unit -> Rectangle
member Width : float
end
type Triangle =
class
inherit Shape
new : sideA:float * sideB:float * sideC:float -> Triangle
override Area : unit -> float
override Draw : unit -> unit
member SideA : float
member SideB : float
member SideC : float
end
val shapes : Shape list
該程式的輸出如下
x.ToString: Circle, area = 78.539816 x.ToString: Circle, area = 452.389342 x.ToString: Square, area = 110.250000 x.ToString: Triangle, area = 6.000000 x.ToString: Rectangle, area = 10.000000