官术网_书友最值得收藏!

3.2 數(shù)據(jù)類的局限性

數(shù)據(jù)類的一個(gè)缺點(diǎn)是不提供封裝。我們看到了編譯器如何為數(shù)據(jù)類生成equals、hashCode和toString方法,但沒有提到它還生成了一個(gè)copy方法,該方法為當(dāng)前的值對(duì)象創(chuàng)建一個(gè)新的副本,并為新副本中的一個(gè)或多個(gè)屬性設(shè)置不同于原來(lái)的值。

例如,以下代碼創(chuàng)建了一個(gè)電子郵件地址的副本,其localPart為postmaster,并且具有和原對(duì)象相同的域:

對(duì)于很多類型來(lái)說(shuō),這非常方便。但是,當(dāng)一個(gè)類對(duì)其內(nèi)部表示進(jìn)行抽象或在其屬性之間維護(hù)不變性時(shí),copy方法允許調(diào)用端代碼直接訪問值的內(nèi)部狀態(tài),這可能會(huì)破壞其不變性。

讓我們看一下Travelator應(yīng)用中的一個(gè)抽象數(shù)據(jù)類型——Money類:

? 構(gòu)造函數(shù)是私有的。其他類通過調(diào)用靜態(tài)的Money.of方法獲取Money值,該方法確保金額的精度與貨幣的最小單位一致。大多數(shù)貨幣都可以換算為100個(gè)最小單位(兩位小數(shù)),但有些貨幣的最小單位數(shù)少一些,有些多一些。例如,日元沒有最小單位,約旦第納爾由1000菲爾組成。

of方法遵循現(xiàn)代Java的編碼約定,它從源頭上區(qū)分了具有身份標(biāo)識(shí)的對(duì)象(由new運(yùn)算符構(gòu)造)和值(從靜態(tài)方法獲得,不可變)。Java的時(shí)間API(例如,LocalDate.of(2020,8,17))和集合API中最新添加的方法(例如,List. of(1,2,3)創(chuàng)建一個(gè)不可變列表)遵循此約定。

該類為String或int金額提供了一些方便的of方法重載。

? Money值基于JavaBean的約定方式公開了其金額和貨幣屬性,盡管它實(shí)際上并不是JavaBean。

? equals和hashCode方法實(shí)現(xiàn)了值語(yǔ)義。

? toString方法返回其屬性的表示,可以展示給用戶,而不僅僅用于調(diào)試。

? Money提供了貨幣價(jià)值計(jì)算的操作。例如,可以將貨幣值相加。add方法通過直接調(diào)用構(gòu)造函數(shù)(而不是使用Money.of)來(lái)構(gòu)造新的Money值,因?yàn)锽igDecimal. add的結(jié)果已經(jīng)有了正確的精度,所以我們可以避免在Money.of中設(shè)置精度的開銷。

BigDecimal.setScale方法令人困惑。盡管其方法名類似于JavaBean的屬性設(shè)置器,但實(shí)際上它并不改變BigDecimal對(duì)象。與EmailAddress和Money類一樣,BigDecimal是一個(gè)不可變的值類型,因此setScale會(huì)返回具有指定精度的新BigDecimal值。

Sun在Java 1.1的標(biāo)準(zhǔn)庫(kù)中加入了BigDecimal類。此版本還包括第一版的JavaBeans API。圍繞Beans API的大肆宣傳使JavaBeans編碼約定被廣泛采用,即便對(duì)于像BigDecimal這種并非JavaBean的類也是如此(參見第1章)。當(dāng)時(shí)并沒有針對(duì)值類型的Java約定。

如今,我們避免使用set前綴為不改變調(diào)用者狀態(tài)的方法命名,而是當(dāng)方法返回一個(gè)對(duì)調(diào)用者的轉(zhuǎn)換時(shí),使用方法名來(lái)強(qiáng)調(diào)其意圖。一個(gè)常見的約定是對(duì)影響單個(gè)屬性的轉(zhuǎn)換使用with前綴,這將使Money類中的代碼變?yōu)椋?/p>

在Kotlin中,可以編寫擴(kuò)展函數(shù)來(lái)修復(fù)此類歷史問題。如果我們正在編寫大量運(yùn)用BigDecimal進(jìn)行計(jì)算的代碼,那么這樣做可以提高代碼的清晰度,可能是值得的:

將Money類轉(zhuǎn)換為Kotlin會(huì)生成以下代碼:

Kotlin類中仍然有一個(gè)主構(gòu)造函數(shù),但該構(gòu)造函數(shù)現(xiàn)在被標(biāo)記為私有的。其語(yǔ)法有點(diǎn)笨拙:我們重新格式化了翻譯器生成的代碼,使其更易于掃描。與EmailAddress.parse一樣,靜態(tài)的of工廠函數(shù)現(xiàn)在是帶有@JvmStatic注解的伴生對(duì)象上的方法。總的來(lái)說(shuō),代碼并沒有比原來(lái)的Java簡(jiǎn)潔多少。

我們可以通過將它變成數(shù)據(jù)類來(lái)進(jìn)一步減少代碼量嗎?

當(dāng)我們將其Money類改為數(shù)據(jù)類時(shí),IntelliJ會(huì)高亮顯示主構(gòu)造函數(shù)的private關(guān)鍵字并發(fā)出警告:

這是怎么回事?

Money類的實(shí)現(xiàn)中隱藏著一個(gè)細(xì)節(jié),其屬性之間保持著一種不變性,確保金額字段的精度等于貨幣字段中最小單位貨幣的默認(rèn)位數(shù)。通過私有構(gòu)造函數(shù)防止Money類之外的代碼創(chuàng)建違反不變性的值。Money.of(BigDecimal,Currency)方法確保其不變性對(duì)于新的Money值是正確的。add方法可以保持該不變性,因?yàn)閷蓚€(gè)具有相同精度的BigDecimal值相加會(huì)產(chǎn)生一個(gè)也具有相同精度的BigDecimal,所以它可以直接調(diào)用構(gòu)造函數(shù)。這樣,構(gòu)造函數(shù)只需要為字段賦值,在知道它永遠(yuǎn)不會(huì)調(diào)用違反其不變性的參數(shù)時(shí)是安全的。

但是,數(shù)據(jù)類的copy方法始終是公開的,這樣會(huì)允許調(diào)用端代碼創(chuàng)建違反不變性的Money值。與EmailAddress不同,像Money這樣的抽象數(shù)據(jù)類型不能實(shí)現(xiàn)為Kotlin的數(shù)據(jù)類。

如果值類型必須在其屬性之間保持不變性,則不要將其定義為數(shù)據(jù)類。

我們可以使用將在后面章節(jié)中遇到的Kotlin功能使類更加簡(jiǎn)潔和方便。因此,我們暫時(shí)放下Money類,第12章將再次討論它,對(duì)它進(jìn)行全面的改進(jìn)。

主站蜘蛛池模板: 河曲县| 界首市| 达日县| 瑞安市| 绍兴市| 府谷县| 富顺县| 枞阳县| 甘肃省| 兴国县| 大名县| 宁阳县| 民乐县| 抚远县| 湾仔区| 宜宾市| 城口县| 调兵山市| 乐至县| 乡宁县| 长沙县| 那坡县| 佳木斯市| 南雄市| 万山特区| 集贤县| 会理县| 绥德县| 合江县| 竹溪县| 射洪县| 商丘市| 壤塘县| 广南县| 宾阳县| 晴隆县| 汕尾市| 利辛县| 河间市| 岐山县| 安化县|