學習Scala中的Rational類:分數(shù)的模型化
Rational類的式樣書
分數(shù):rational number是一種可以表達為比率 的數(shù)字,這里的n和d是數(shù)字,其中d不能為零。n被稱作是分子:numerator,d被稱作是分母:denominator。分數(shù)的例子有:
,
,
和
。與浮點數(shù)相比較,分數(shù)的優(yōu)勢是小數(shù)部分得到了完全表達,沒有舍入或估算。
51CTO編輯推薦:Scala編程語言專題
本章我們將要設(shè)計的類必須模型化分數(shù)的行為,包括允許它們執(zhí)行加,減,乘還有除運算。要加兩個分數(shù),首先要獲得公分母,然后才能把兩個分子相加。例如,要計算 ,先把左操作數(shù)的上下部分都乘上3,右操作數(shù)的兩部分都乘上2,得到了
。把兩個分子相加產(chǎn)生結(jié)果,
。要乘兩個分數(shù),可以簡單的兩個分子相乘,然后兩個分母相乘。因此,
得到了
,還可以簡化表示成它的“通常”形式
。除法是把右操作數(shù)分子分母調(diào)換,然后做乘法。例如
與
相同,結(jié)果是
。
一個或許不怎么重要的發(fā)現(xiàn)是,在數(shù)學上,分數(shù)不具有可變的狀態(tài)。一個分數(shù)加到另外一個分數(shù)上,產(chǎn)生的結(jié)果是一個新的分數(shù)。而原來的數(shù)不會被“改變”。我們將在本章設(shè)計的不可變的Rational類將秉承這一屬性。每個分數(shù)將都被表示成一個Rational對象。當兩個Rational對象相加時,一個新的帶著累加結(jié)果的Rational對象將被創(chuàng)建出來。
本章還將捎帶提一些Scala讓你寫出感覺像原生語言支持的庫的方法。例如,在本章結(jié)尾你將能用Rational類這樣做:
創(chuàng)建Rational類
- scala> val oneHalf = new Rational(1, 2)
- oneHalf: Rational = 1/2
- scala> val twoThirds = new Rational(2, 3)
- twoThirds: Rational = 2/3
- scala> (oneHalf / 7) + (1 twoThirds)
- res0: Rational = 17/42
開始設(shè)計Rational類的著手點是考慮客戶程序員將如何創(chuàng)建一個新的Rational對象。假設(shè)我們已決定讓Rational對象是不可變的,我們將需要那個客戶在創(chuàng)建實例時提供所有需要的數(shù)據(jù)(本例中,是分子和分母)。因此,我們應(yīng)該這么開始設(shè)計:
這行代碼里首先應(yīng)當注意到的是如果類沒有主體,就不需要指定一對空的大括號(當然你如果想的話也可以)。在類名,Rational,之后括號里的n和d,被稱為類參數(shù):class parameter。Scala編譯器會收集這兩個類參數(shù)并創(chuàng)造一個帶同樣的兩個參數(shù)的主構(gòu)造器:primary constructor。
- class Rational(n: Int, d: Int)
不可變對象的權(quán)衡
不可變對象提供了若干強于可變對象的優(yōu)點和一個潛在的缺點。首先,不可變對象常常比可變對象更具邏輯性,因為它們沒有隨著時間而變化的復雜的狀態(tài)空間。其次,你可以很自由地傳遞不可變對象,而或許需要在把可變對象傳遞給其它代碼之前,需要先建造個以防萬一的副本。第三,沒有機會能讓兩個同時訪問不可變對象的線程破壞它合理構(gòu)造的狀態(tài),因為根本沒有線程可以改變不可變對象的狀態(tài)。第四,不可變對象讓哈希表鍵值更安全。比方說,如果可變對象在被放進了HashSet之后被改變,那么你下一次查找這個HashSet就找不到這個對象了。
不可變對象唯一的缺點就是它們有時需要復制很大的對象圖而可變對象的更新可以在原地發(fā)生。有些情況下這會變得難以快速完成而可能產(chǎn)生性能瓶頸。結(jié)果,要求庫提供可變替代以使其更容易在大數(shù)據(jù)結(jié)構(gòu)的中間改變一些元素也并非是一件稀奇的事情。例如,類StringBuilder是不可變的String的可變替代。
注意
這個最初的Rational例子凸顯了Java和Scala之間的不同。Java類具有可以帶參數(shù)的構(gòu)造器,而Scala類可以直接帶參數(shù)。Scala的寫法更簡潔——類參數(shù)可以直接在類的主體中使用;沒必要定義字段然后寫賦值函數(shù)把構(gòu)造器的參數(shù)復制到字段里。這可以潛在地節(jié)省很多固定寫法,尤其是對小類來說。
Scala編譯器將把你放在類內(nèi)部的任何不是字段的部分或者方法定義的代碼,編譯進主構(gòu)造器。例如,你可以像這樣打印輸出一條除錯消息:
根據(jù)這個代碼,Scala編譯器將把println調(diào)用放在Rational的主構(gòu)造器。因此,println調(diào)用將在每次創(chuàng)建一個新的Rational實例時打印這條除錯信息:
- class Rational(n: Int, d: Int) {
- println("Created "+n+"/"+d)
- }
- scala> new Rational(1, 2)
- Created 1/2
- res0: Rational = Rational@a0b0f5
【相關(guān)閱讀】