F#探险之旅(四):面向对象编程(中)
F#系列隨筆索引
對(duì)象表達(dá)式(Object Expressions)
F#中的OOP語法很簡(jiǎn)潔,而對(duì)象表達(dá)式則正是這種簡(jiǎn)潔性的核心所在。通過對(duì)象表達(dá)式,我們可以創(chuàng)建抽象類或接口的輕量級(jí)實(shí)現(xiàn),也可以對(duì)一個(gè)具體的類進(jìn)行繼承。換言之,可以在實(shí)現(xiàn)抽象類或接口,或者繼承具體類的同時(shí)創(chuàng)建新類型的一個(gè)實(shí)例。下面來看如何對(duì)泛型接口IComparer<’T>應(yīng)用對(duì)象表達(dá)式。
#light
open System
open System.Collections.Generic
let revStringComparer =
{ new IComparer<string>
with
Compare(s1, s2) =
let rev (s : string) =
new string(Array.rev(s.ToCharArray()))
let reversed = rev s1
reversed.CompareTo(rev s2)
}
let winners = [| "Sandie Shaw"; "Bucks Fizz"; "Dana International"; "Abba" |]
print_any winners
print_newline()
Array.Sort(winners, revStringComparer)
print_any winners
運(yùn)行結(jié)果為
[|"Sandie Shaw"; "Bucks Fizz"; "Dana International"; "Abba"|]
[|"Abba"; "Dana International"; "Sandie Shaw"; "Bucks Fizz"|]
這里演示了實(shí)現(xiàn)IComparer的過程,該接口有一個(gè)方法Compare,它接受兩個(gè)參數(shù)并通過返回值來表示參數(shù)比較的結(jié)果。它是泛型接口,這里的類型參數(shù)為string,可以在標(biāo)識(shí)符revStringComparer定義的第二行看到。從標(biāo)識(shí)符的名字也可以了解到,它是將參數(shù)顛倒后進(jìn)行比較,運(yùn)行結(jié)果的第二行印證了這一點(diǎn)。可以看到“在實(shí)現(xiàn)接口的同時(shí)返回一個(gè)實(shí)例”。
看看多重繼承的情況。在C#中,一個(gè)類不能繼承多個(gè)類,卻可以同時(shí)實(shí)現(xiàn)多個(gè)接口,F#也是一樣的。要注意的是,如果同時(shí)繼承類并實(shí)現(xiàn)接口,須將類的部分房子最前面;而且必須在第一個(gè)類/接口定義完畢之后才能開始第二個(gè)接口的實(shí)現(xiàn)。
#light
open System
open System.Drawing
open System.Windows.Forms
let makeNumberControl(n: int) =
{ new Control(Tag = n, Width = 32, Height = 16)
with
override x.OnPaint(e) =
let font = new Font(FontFamily.Families.[1], 12.0F)
e.Graphics.DrawString(n.ToString(), font, Brushes.Black,
new PointF(0.0F, 0.0F))
interface IComparable
with
CompareTo(other) =
let otherCtrl = other :?> Control in
let n1 = otherCtrl.Tag :?> int in
n.CompareTo(n1) }
let numberCtrls =
let temp = new ResizeArray<Control>()
let random = new Random()
for index = 1 to 10 do
temp.Add(makeNumberControl(random.Next(100)))
temp.Sort()
let height = ref 0
temp |> Seq.iter
(fun c ->
c.Top <- !height
height := c.Height + !height)
temp.ToArray()
let myForm =
let temp = new Form() in
temp.Controls.AddRange(numberCtrls);
temp
[<STAThread>]
do Application.Run(myForm)
在對(duì)象表達(dá)式makeNumberControl中,它繼承了Control類,同時(shí)也實(shí)現(xiàn)了IComparable接口。這里可以看到,在CompareTo方法中根據(jù)控件的Tag值進(jìn)行比較,在調(diào)用Sort方法(ResizeArray即泛型類List<’T>)時(shí)會(huì)根據(jù)Compare方法對(duì)控件進(jìn)行排序。排序完成后,依次呈現(xiàn)這些控件,如下圖:
對(duì)象表達(dá)式是一種強(qiáng)大的機(jī)制,可以幫助我們快速而簡(jiǎn)潔地將非F#庫中的對(duì)象引入F#代碼。它的缺點(diǎn)則是沒法添加額外的屬性或方法。這也是在前面的例子中為何使用Tag屬性來存放n的值。
定義接口
接口僅包含抽象的方法和屬性。它為所有實(shí)現(xiàn)它的類定義了一份“契約”。F#中接口的概念與C#中的相同,在此不再贅述。如:
type MyInterface = interface
abstract ChangeState : int -> unit
end
實(shí)現(xiàn)接口
實(shí)現(xiàn)接口的語法也很簡(jiǎn)單。仍以上面的接口為例:
type MyImpl = class
val mutable state : int
new() = { state = 0 }
interface MyInterface with
member x.ChangeState y = x.state <- y
end
end
let impl = new MyImpl()
let inter = impl :> MyInterface
let printIntAndNewLine i =
print_int i
print_newline()
let main() =
inter.ChangeState 1
printIntAndNewLine impl.state
inter.ChangeState 2
printIntAndNewLine impl.state
main()
運(yùn)行結(jié)果為:
1
2
不知你有沒有注意到,在調(diào)用ChangeState方法前,我們先將impl轉(zhuǎn)換為了MyInterface類型,否則不能調(diào)用。這是因?yàn)榻涌谠贔#中是顯式實(shí)現(xiàn)的,如果希望不經(jīng)轉(zhuǎn)換就可以直接調(diào)用接口的方法,只能在類中在顯式地添加這個(gè)方法:-(
類、字段和顯式的構(gòu)造函數(shù)
前面我們主要還是使用非F#庫中的類,現(xiàn)在來看看如何定義自己的類。
#light
type EmptyClass = class
end
let emptyItem = new EmptyClass()
嗯,很明顯,這里試圖定義一個(gè)類,然后創(chuàng)建它的一個(gè)實(shí)例。不過編譯器不允許你這么做,它會(huì)告訴你:“error FS0191: No constructors are available for the type 'EmptyClass'”。沒有構(gòu)造函數(shù)?如果你是C#程序員就會(huì)覺得奇怪了。事實(shí)上,F#沒有提供默認(rèn)的構(gòu)造函數(shù)這種機(jī)制,我們必須得手工添加一個(gè),構(gòu)造函數(shù)的名字總是為new。
type EmptyClass = class
new() = {}
end
另外,默認(rèn)的構(gòu)造函數(shù)容易使得字段不能正確地初始化,會(huì)給程序帶來隱患,而在F#中的構(gòu)造函數(shù)必須初始化所有字段。使用val關(guān)鍵字定義字段,在下面的類MyFile中,它擁有兩個(gè)字段,path和innerFile,兩個(gè)字段在構(gòu)造函數(shù)內(nèi)進(jìn)行初始化。
#light
open System.IO
type MyFile = class
val path : string
val innerFile : FileInfo
new() = new MyFile("default.txt")
new(p) =
{ path = p;
innerFile = new FileInfo(p) }
end
我們還可以看到,這個(gè)類有兩個(gè)構(gòu)造函數(shù),也就是說構(gòu)造函數(shù)可以重載。觀察構(gòu)造函數(shù)new(p),它有一部分在{}內(nèi),這個(gè)代碼塊稱為構(gòu)造函數(shù)的初始化塊,在這里唯一能做的事情就是初始化字段。如果想做更多的事情,就要在后面加then添加其它代碼:
type MyFile2 = class
val path : string
val innerFile : FileInfo
new(p) as x =
{ path = p;
innerFile = new FileInfo(p) }
then
if not x.innerFile.Exists then
let textFile = x.innerFile.CreateText()
textFile.Dispose()
end
new(p)后面加了as x,這樣就可以在后面的代碼中引用當(dāng)前的對(duì)象了。then后面的代碼首先檢查文件是否存在,如果不存在就創(chuàng)建一個(gè)新文件。
注意:上面兩個(gè)類MyFile和MyFile2中的字段都是只讀的,如果需要修改它們,可以在字段名字前面添加關(guān)鍵字mutable,如val mutable innerFile;同時(shí)它們的訪問修飾符都是public,在下一篇文章將介紹如何在類中定義屬性。
思考:在函數(shù)式編程(下)中,曾介紹過自定義的記錄類型,比如
type couple = { him : string; her : string }
那么這里的couple類和上面的MyFile類主要有哪些區(qū)別呢?請(qǐng)出我們的老朋友.NET Reflector來吧。在Reflector中打開編譯過的F#程序集可以看到,couple的類型定義為:
public sealed class couple : IStructuralHash, IComparable
這是一個(gè)sealed類,這意味著無法繼承它,其中的her和him都是只讀屬性。
而MyFile的定義則是:
public class MyFile
這個(gè)就跟C#中常規(guī)類的定義一致了,其中的path和innerFile都是只讀屬性。
隱式的類構(gòu)造(Implicit Class Construction)
除了上面的顯式構(gòu)造函數(shù),F#還支持隱式的類構(gòu)造語法,這樣的語法更為緊湊。它允許在定義類的成員前執(zhí)行一系列的let值綁定語句,這些綁定屬于類的私有部分。
#light
open System.IO
type MyOtherFile(path) = class
let innerFile = new FileInfo(path)
member x.InnerFile = innerFile
end
我覺得對(duì)于前面的(顯式的類構(gòu)造)MyFile類定義,這里的MyOtherFile類當(dāng)然更為緊湊,看起來在定義類的同時(shí)就定義了構(gòu)造函數(shù),隨后馬上初始化了字段,不過這里的innerFile已經(jīng)是私有的了,所以再添加一個(gè)屬性InnerFile來公開innerFile字段。
隱式的類構(gòu)造要比等價(jià)的顯式類構(gòu)造代碼少很多,但是有時(shí)必須要用顯式的類構(gòu)造,比如編寫擁有多個(gè)構(gòu)造函數(shù)的類的時(shí)候。
小結(jié)
本文首先介紹了強(qiáng)大的對(duì)象表達(dá)式機(jī)制,通過它,我們可以快速地創(chuàng)建抽象類或接口的輕量級(jí)實(shí)現(xiàn);接下來是定義和實(shí)現(xiàn)接口;最后介紹了如何創(chuàng)建和實(shí)例化一個(gè)類,在創(chuàng)建類實(shí)例的時(shí)候,我們既可以采用顯式的構(gòu)造函數(shù),也可以采用更為緊湊的“隱式的類構(gòu)造”機(jī)制。
F#系列隨筆索引
注意:本文中的代碼均在F# 1.9.4.17版本下編寫,在F# CTP 1.9.6.0版本下可能不能通過編譯。
參考:
《Foundations of F#》 by Robert Pickering
《Expert F#》 by Don Syme , Adam Granicz , Antonio Cisternino
《F# Specs》
總結(jié)
以上是生活随笔為你收集整理的F#探险之旅(四):面向对象编程(中)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CCNP学习笔记(5)
- 下一篇: linux Firefox汉化