F#编程语言中的抽象方法、虚拟方法和接口实现

F#中,可以像在.NET的其他语言中一样,将一个方法(函数)标记为抽象的。不过,有一点微妙的差别在于,即使不提供默认实现,也必须将成员声明为抽象的。可以将没有默认实现的抽象方法视为抽象方法,而将带有默认实现的抽象成员视为虚拟成员。F#中没有virtual关键字。

要实现一个纯粹的抽象方法(即没有默认实现的方法),需要使用特殊的AbstractClass属性,否则将在编译时遇到错误。现在先忽略这一点,专注于一些实际的代码:

虚拟方法

以下是一个具有默认实现的抽象方法示例(使用default关键字实现),这在C#中相当于虚拟方法。示例如下:

type SomeBaseClass() = let mutable z = 0 abstract member SomeVirtualMethod : int -> int default this.SomeVirtualMethod(a : int) = z <- z + a z type SomeDerivedClass() = inherit SomeBaseClass() override this.SomeVirtualMethod(a : int) = a * 2

可以看到,在SomeDerived类中,可以使用override关键字来覆盖基类实现,这与.NET其他语言中的操作方式非常相似,是有选择性地为要覆盖的方法提供新的实现。在F#中,与C#等语言相比,缺少了一些更精细的控制,比如使用“new”关键字隐藏特定方法的实现,或者在覆盖的方法中调用原始基类方法。

以下是虚拟方法的使用示例:

let foo = new SomeDerivedClass() printfn "foo.SomeVirtualMethod(24) %A" (foo.SomeVirtualMethod(24))

运行后将得到如下结果:

抽象方法

刚刚讨论了F#中“虚拟”成员的等价物,它们是抽象的但同时也提供了默认实现。但是,对于那些没有提供默认实现的抽象成员,该如何处理呢?这里有一个小型示例,这次必须使用AbstractClass属性,以表示整个类是抽象的。这允许定义一个类型,其中包含没有实现的抽象成员,实际的实现将由抽象类的继承者提供。

[<AbstractClass>] type SomeBaseClass() = abstract member SomeAbstractMethod : int -> unit type SomeDerivedClass() = inherit SomeBaseClass() override this.SomeAbstractMethod(a : int) = printfn "a was %A" a |> ignore

以下是抽象类/方法的使用示例:

let foo = new SomeDerivedClass() printfn "foo.SomeAbstractMethod(24)" |> ignore do foo.SomeAbstractMethod(24)

运行后将得到如下结果:

调用基类构造函数

必须在派生类中调用基类的构造函数。基类构造函数的参数出现在inherit子句的参数列表中。在F#中这是可行的,尽管它肯定没有C#中那么漂亮。已经看到了一个没有参数的简单构造函数的情况,但是如果基类型包含有参数的构造函数,或者甚至有多个构造函数,该如何处理呢?认为最好的方法是通过一个示例来展示如何处理这种情况。因此,这里有一个基类型具有混合构造函数的示例,然后被继承。

type SomeBaseClass = val mutable stringField : string new (s) = { stringField = s } new () = { stringField = "" } type DerivedClass = inherit SomeBaseClass val mutable stringField2 : string new (s1, s2) = { inherit SomeBaseClass(s1); stringField2 = s2 } new (s2) = { inherit SomeBaseClass(); stringField2 = s2 } new () = { inherit SomeBaseClass(); stringField2 = "" }

如所见,这里有更多的仪式性处理,比如大括号“{}”和在需要调用基类构造函数的地方额外使用inherit关键字。尽管如此,一旦习惯了,认为这并不是那么糟糕。

对象表达式

有时可能只需要进行微小的更改,在这种情况下,F#提供了一种替代继承的方法,称为“对象表达式”技术。这里有一个微不足道的示例:

let public myObjectExpressionObject = { new Object() with override this.ToString() = "Override the object.ToString() method" }

可以看到,所要做的只是为用途提供一个全新的ToString()方法,但并没有从任何东西继承,事实上那里没有自定义类型,只是一个let绑定和一个对象表达式,覆盖了对象类型的ToString()方法。

可以这样使用它:

let result = myObjectExpressionObject.ToString() printfn "myObjectExpressionObject.ToString() = %A" result

运行后将得到预期的结果:

要定义一个接口,只需声明一个具有抽象成员的类型。要实现一个接口,只需使用interface XXX with语法,然后为接口的原始成员提供成员细节。这里有一个小型示例。

type IOrderDetails = abstract member Describe : unit -> string type Order(x : int, y : string, z : DateTime) = interface IOrderDetails with member this.Describe() = String.Format("{0} : {1} x {2}", z.ToShortTimeString(), x, y)

这声明了一个非常简单的接口IOrderDetails,它有一个成员。然后提供了一个自定义的Order类型,它实现了IOrderDetails接口。

这就是如何声明一个接口,但在某一点上,将希望能够调用已经实现的接口方法,那么如何做到这一点呢?如果创建一个新的Order对象并查看智能感知,可以看到IOrderDetails.Describe成员并没有列出:

也许需要某种类型的转换,将Order实例转换为它实现的IOrderDetails接口。是的,这是正确的答案。在C#中,有两个选择,可以使用转换运算符((IOrderDetails)o).Describe(),或者可以使用as关键字,可以像这样使用:(o as IOrderDetails).Describe()。然而,不是使用C#,而是使用F#,所以需要关注在F#中需要做什么。

let o = new Order(1, "Star Wars DVD", DateTime.Now) printfn "((o :> IOrderDetails).Describe()) = %A" ((o :> IOrderDetails).Describe())
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485