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