在實現(xiàn)具體應(yīng)用前旬迹,先介紹下隱式類的概念。
隱式類介紹
Scala 2.10引入了一種叫做隱式類的新特性求类。隱式類指的是用implicit關(guān)鍵字修飾的類奔垦。在對應(yīng)的作用域內(nèi),帶有這個關(guān)鍵字的類的主構(gòu)造函數(shù)可用于隱式轉(zhuǎn)換仑嗅。
用法
創(chuàng)建隱式類時宴倍,只需要在對應(yīng)的類前加上implicit關(guān)鍵字。比如:
object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = {
def loop(current: Int): Unit =
if(current > 0) {
f
loop(current - 1)
}
loop(x)
}
}
}
這個例子創(chuàng)建了一個名為IntWithTimes的隱式類仓技。這個類包含一個int值和一個名為times的方法鸵贬。要使用這個類,只需將其導(dǎo)入作用域內(nèi)并調(diào)用times方法脖捻。比如:
scala> import Helpers._
import Helpers._
scala> 5 times println("HI")
HI
HI
HI
HI
HI
使用隱式類時阔逼,類名必須在當(dāng)前作用域內(nèi)可見且無歧義,這一要求與隱式值等其他隱式類型轉(zhuǎn)換方式類似地沮。
限制條件
隱式類有以下限制條件:
- 只能在別的trait/類/對象內(nèi)部定義嗜浮。
object Helpers {
implicit class RichInt(x: Int) // 正確!
}
implicit class RichDouble(x: Double) // 錯誤摩疑!
- 構(gòu)造函數(shù)只能攜帶一個非隱式參數(shù)危融。
implicit class RichDate(date: java.util.Date) // 正確!
implicit class Indexer[T](collecton: Seq[T], index: Int) // 錯誤雷袋!
implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正確吉殃!
雖然我們可以創(chuàng)建帶有多個非隱式參數(shù)的隱式類,但這些類無法用于隱式轉(zhuǎn)換楷怒。
- 在同一作用域內(nèi)蛋勺,不能有任何方法、成員或?qū)ο笈c隱式類同名鸠删。注意:這意味著隱式類不能是case class抱完。
object Bar
implicit class Bar(x: Int) // 錯誤!
val x = 5
implicit class x(y: Int) // 錯誤刃泡!
implicit case class Baz(x: Int) // 錯誤巧娱!
現(xiàn)在,我們來實現(xiàn)一個簡單的DSL捅僵。通常家卖,我們在計算貨幣運算時,如果不涉及到幣種的轉(zhuǎn)換庙楚,通常可以簡單的用數(shù)學(xué)表達(dá)式計算趴樱,比如23 + 12
馒闷,在任何編程語言里都能做到酪捡。但是涉及到幣種時,這種簡單的表達(dá)式不滿足要求了纳账,需要做一下匯率轉(zhuǎn)換才能進(jìn)行計算逛薇,然而這樣卻不很直觀。我們需要一種直觀的運算疏虫,同時又要包含匯率轉(zhuǎn)換的操作永罚,比如像這樣的表達(dá)式:23(USD) + 12(EUR)
,下面卧秘,我們就用scala的隱式轉(zhuǎn)換實現(xiàn)這樣的要求呢袱。
首先,我們定義一個關(guān)于貨幣的伴生類(class)和伴生對象(單例對象object)翅敌。定義它們的成員變量code羞福,name以及object中對應(yīng)的幣種信息。關(guān)鍵代碼如下:
object Currency {
type Rate = Map[(Currency, Currency), BigDecimal] //first_currency / second_currency = rate: BigDecimal
def apply (code: String, name: String) = new Currency(code, name)
/*
def apply (code: String) = code.toLowerCase match {
case "USD" => USD
case "EUR" => EUR
case "GBP" => GBP
}
*/
lazy val USD: Currency = Currency("USD", "美元")
lazy val EUR: Currency = Currency("EUR", "歐元")
lazy val GBP: Currency = Currency("GBP", "英鎊")
def convert (from: Currency, to: Currency, rate: Rate): BigDecimal = {
if (from.code.equalsIgnoreCase(to.code))
1
else
rate.getOrElse((from, to), 1 / rate((to, from)))
}
implicit class BigDecimalExt (value: BigDecimal) {
def apply(currency: Currency)(implicit rate: Rate): Money = Money(currency, value)
}
implicit class IntExt (value: Int) {
def apply(currency: Currency)(implicit rate: Rate): Money = (value: BigDecimal).apply(currency)
}
implicit class CurrIntExt (c: Currency) {
def apply(value: Int)(implicit rate: Rate): Money = Money(c, value)(rate)
}
}
class Currency (val code: String, val name: String = "未知")
object代碼中蚯涮,我們定義了匯率的類型Rate治专,也就是匯率等于先后兩個幣種的比值。convert方法用于后面金錢的具體轉(zhuǎn)換遭顶。
然后定義Money類:
case class Money (currency: Currency,amount: BigDecimal)(implicit rate: Rate) {
def to (to: Currency): Money = {
val rates = Currency.convert(currency, to, rate)
Money(to, rates * amount)
}
def operation (that: Money, operate: (BigDecimal, BigDecimal) => BigDecimal): Money = {
that match {
case Money(curr, am) if (currency.code equalsIgnoreCase curr.code) => Money(currency, operate(amount, am))
case Money(curr, am) => operation(that.to(currency), operate)
}
}
def + (that: Money): Money = {
operation(that, (a, b)=> a + b) //占位符寫法 operation(that, _ + _)
}
}
Money類中张峰,方法to(to: Curreyct)
用于幣種轉(zhuǎn)換,+
用于幣種計算棒旗,此處僅僅實現(xiàn)了+
方法喘批。
至此,我們實現(xiàn)了不同幣種加法的運算嗦哆。利用上面的代碼谤祖,我們可以有三種不同的調(diào)用方法。
-
傳統(tǒng)的方法調(diào)用
val rate_0: Rate = Map(
(GBP, EUR) -> 1.39,
(EUR, USD) -> 1.08,
(GBP, USD) -> 1.5
)val r0 = (Money(USD, 42)(rate_0) + Money(EUR, 35)(rate_0)) to GBP println("r0: " + r0.currency.name + " " + r0.currency.code + " " + r0.amount)
-
兩種DSL方式寫法(涉及到Currency里的隱式類)
implicit val rate_1: Rate = Map(
(GBP, EUR) -> 1.39,
(EUR, USD) -> 1.08,
(GBP, USD) -> 1.5
)//for r1 and r2import Currency.IntExt val r1 = (42(USD) + 35(EUR)) to GBP println("r1: " + r1.currency.name + " " + r1.currency.code + " " + r1.amount) import Currency.CurrIntExt val r2 = (USD(42) + EUR(35)) to GBP println("r2: " + r2.currency.name + " " + r2.currency.code + " " + r2.amount)
小記:雖然scala隱式轉(zhuǎn)換是一個非常強(qiáng)大的功能老速,但是實現(xiàn)起來很困難粥喜,dsl雖然使用方便,但是這樣的“魔幻”代碼理解起來還是費勁橘券。