跳转至内容

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

公有、私有和受保护成员

[编辑 | 编辑源代码]
前一页:类 索引 下一页:接口
华夏公益教科书