继承
在面向对象的编程中,继承用于建模“is-a”关系或子类型化。
指定继承关系
可通过在类声明中使用 inherit
关键字来指定继承关系。 下面的示例显示了基本语法形式。
type MyDerived(...) =
inherit MyBase(...)
一个类最多只能有一个直接基类。 如果未使用 inherit
关键字指定基类,则该类将隐式继承自 System.Object
。
继承成员
如果一个类继承自另一个类,则派生类的用户可以使用基类的方法和成员,就像它们是派生类的直接成员一样。
任何 let 绑定和构造函数参数都是类的专用绑定和参数,因此不能通过派生类访问。
关键字 base
可用于派生类,并引用基类实例。 它的用法类似于自我标识符。
虚拟方法和替代
虚拟方法(和属性)在 F# 中的工作方式与在其他 .NET 语言中有所不同。 若要声明新的虚拟成员,可以使用 abstract
关键字。 无论是否为该方法提供了默认实现,都可以执行此操作。 因此,基类中虚拟方法的完整定义遵循以下模式:
abstract member [method-name] : [type]
default [self-identifier].[method-name] [argument-list] = [method-body]
在派生类中,此虚拟方法的替代遵循以下模式:
override [self-identifier].[method-name] [argument-list] = [method-body]
如果省略基类中的默认实现,基类将成为抽象类。
下面的代码示例演示了基类中新虚拟方法 function1
的声明,以及如何在派生类中替代它。
type MyClassBase1() =
let mutable z = 0
abstract member function1: int -> int
default u.function1(a: int) =
z <- z + a
z
type MyClassDerived1() =
inherit MyClassBase1()
override u.function1(a: int) = a + 1
构造函数和继承
必须在派生类中调用基类的构造函数。 基类构造函数的参数出现在 inherit
子句的参数列表中。 必须通过提供给派生类构造函数的参数确定所使用的值。
下面的代码显示了基类和派生类,其中派生类调用 inherit 子句中的基类构造函数:
type MyClassBase2(x: int) =
let mutable z = x * x
do
for i in 1..z do
printf "%d " i
type MyClassDerived2(y: int) =
inherit MyClassBase2(y * 2)
do
for i in 1..y do
printf "%d " i
对于多个构造函数,可以使用以下代码。 派生类构造函数的第一行是 inherit
子句,字段显示为使用 val
关键字声明的显式字段。 有关详细信息,请参阅显式字段:val
关键字。
type BaseClass =
val string1 : string
new (str) = { string1 = str }
new () = { string1 = "" }
type DerivedClass =
inherit BaseClass
val string2 : string
new (str1, str2) = { inherit BaseClass(str1); string2 = str2 }
new (str2) = { inherit BaseClass(); string2 = str2 }
let obj1 = DerivedClass("A", "B")
let obj2 = DerivedClass("A")
继承的替代项
在需要对类型进行细微修改的情况下,考虑使用对象表达式作为继承的替代项。 下面的示例演示了如何使用对象表达式作为替代方法来创建新的派生类型:
open System
let object1 =
{ new Object() with
override this.ToString() = "This overrides object.ToString()" }
printfn "%s" (object1.ToString())
有关对象表达式的详细信息,请参阅对象表达式。
在创建对象层次结构时,考虑使用可区分联合而不是继承。 可区分联合还可以对共享常见总体类型的不同对象的各种行为进行建模。 单个可区分联合通常可以避免需要多个派生类,这些派生类是彼此的细微变化形式。 有关可区分联合的信息,请参阅可区分联合。