抽象类 (F#)
抽象类是一些留有部分或全部成员未实现的类,以便可以由派生类来提供实现。
// Abstract class syntax.
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
[ inherit base-class-or-interface-name ]
[ abstract-member-declarations-and-member-definitions ]
// Abstract member syntax.
abstract member member-name : type-signature
备注
在面向对象的编程中,抽象类用作层次结构的基类,并表示不同对象类型组的通用功能。 正如名称“抽象”所暗指的,抽象类通常不会直接与问题域中的具体实体对应。 不过,抽象类会表示多个不同的具体实体之间的共同之处。
抽象类必须具有 AbstractClass 特性, 并可以包含已实现的成员和尚未实现的成员。 在对类应用术语“抽象”时,该术语的用法与在其他 .NET 语言中的用法一样;但是,在对方法(和属性)应用术语“抽象”时,该术语在 F# 中的用法与在其他 .NET 语言中的用法会略有不同。 在 F# 中,当用 abstract 关键字标记一个方法时,这将指示一个成员在该类型的虚函数的内部表中有一个称作“虚拟调度槽”的项。 换言之,该方法是虚方法(尽管 F# 语言中未使用 virtual 关键字)。 应对虚方法使用关键字 abstract,而不管虚方法是否已实现。 虚拟调度槽的声明与该调度槽的方法的定义是分开的。 因此,在另一种 .NET 语言中,虚方法声明和定义的 F# 等效项是抽象方法声明和单独定义的组合(使用 default 关键字或 override 关键字)。 有关更多信息和示例,请参见方法 (F#)。
仅当存在已声明但未定义的抽象方法时,才会将类视为抽象类。 因此,具有抽象方法的类并不一定是抽象类。 除非类具有未定义的抽象方法,否则不要使用 AbstractClass 特性。
在前面的语法中,accessibility-modifier 可以为 public、private 或 internal。 有关更多信息,请参见访问控制 (F#)。
与其他类型一样,抽象类可以具有一个基类以及一个或多个基接口。 每个基类或接口将与 inherit 关键字一起出现在单独的行上。
抽象类的类型定义可以包含已完全定义的成员,但也可以包含抽象成员。 前面的语法中单独显示了针对抽象成员的语法。 在此语法中,成员的 type signature 是一个列表,其中包含参数类型(按顺序)和返回类型,并用适合扩充参数和元组参数的 -> 标记和/或 * 标记来分隔这些类型。 抽象成员类型签名的语法与签名文件中使用的语法以及 IntelliSense 在 Visual Studio 代码编辑器显示的语法相同。
示例
下面的代码阐释了抽象类 Shape,该类具有两个非抽象派生类,即 Square 和 Circle。 该示例演示如何使用抽象类、方法和属性。 在该示例中,抽象类 Shape 表示圆形和正方形这两个具体实体的通用元素。 所有形状的通用特性(在二维坐标系中)都抽象到 Shape 类中:网格中的位置、旋转角度、面积和周长属性。 除了各个形状无法更改的位置行为之外,可以对上述属性进行重写。
如 Circle 类中所示,可以对其中的旋转方法进行重写。由于圆形是完全对称的,不会随旋转而变化, 因此,在 Circle 类中,旋转方法将替换为一个不执行任何操作的方法。
// An abstract class that has some methods and properties defined
// and some left abstract.
[<AbstractClass>]
type Shape2D(x0 : float, y0 : float) =
let mutable x, y = x0, y0
let mutable rotAngle = 0.0
// These properties are not declared abstract. They
// cannot be overriden.
member this.CenterX with get() = x and set xval = x <- xval
member this.CenterY with get() = y and set yval = y <- yval
// These properties are abstract, and no default implementation
// is provided. Non-abstract derived classes must implement these.
abstract Area : float with get
abstract Perimeter : float with get
abstract Name : string with get
// This method is not declared abstract. It cannot be
// overriden.
member this.Move dx dy =
x <- x + dx
y <- y + dy
// An abstract method that is given a default implementation
// is equivalent to a virtual method in other .NET languages.
// Rotate changes the internal angle of rotation of the square.
// Angle is assumed to be in degrees.
abstract member Rotate: float -> unit
default this.Rotate(angle) = rotAngle <- rotAngle + angle
type Square(x, y, sideLengthIn) =
inherit Shape2D(x, y)
member this.SideLength = sideLengthIn
override this.Area = this.SideLength * this.SideLength
override this.Perimeter = this.SideLength * 4.
override this.Name = "Square"
type Circle(x, y, radius) =
inherit Shape2D(x, y)
let PI = 3.141592654
member this.Radius = radius
override this.Area = PI * this.Radius * this.Radius
override this.Perimeter = 2. * PI * this.Radius
// Rotating a circle does nothing, so use the wildcard
// character to discard the unused argument and
// evaluate to unit.
override this.Rotate(_) = ()
override this.Name = "Circle"
let square1 = new Square(0.0, 0.0, 10.0)
let circle1 = new Circle(0.0, 0.0, 5.0)
circle1.CenterX <- 1.0
circle1.CenterY <- -2.0
square1.Move -1.0 2.0
square1.Rotate 45.0
circle1.Rotate 45.0
printfn "Perimeter of square with side length %f is %f, %f"
(square1.SideLength) (square1.Area) (square1.Perimeter)
printfn "Circumference of circle with radius %f is %f, %f"
(circle1.Radius) (circle1.Area) (circle1.Perimeter)
let shapeList : list<Shape2D> = [ (square1 :> Shape2D);
(circle1 :> Shape2D) ]
List.iter (fun (elem : Shape2D) ->
printfn "Area of %s: %f" (elem.Name) (elem.Area))
shapeList