在实现具体应用前,先介绍下隐式类的概念。
隐式类介绍
Scala 2.10引入了一种叫做隐式类的新特性。隐式类指的是用implicit关键字修饰的类。在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换。
用法
创建隐式类时,只需要在对应的类前加上implicit关键字。比如:
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)
}
}
}
这个例子创建了一个名为IntWithTimes的隐式类。这个类包含一个int值和一个名为times的方法。要使用这个类,只需将其导入作用域内并调用times方法。比如:
scala> import Helpers._
import Helpers._
scala> 5 times println("HI")
HI
HI
HI
HI
HI
使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
限制条件
隐式类有以下限制条件:
- 只能在别的trait/类/对象内部定义。
object Helpers {
implicit class RichInt(x: Int) // 正确!
}
implicit class RichDouble(x: Double) // 错误!
- 构造函数只能携带一个非隐式参数。
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) // 正确!
虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。
- 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。注意:这意味着隐式类不能是case class。
object Bar
implicit class Bar(x: Int) // 错误!
val x = 5
implicit class x(y: Int) // 错误!
implicit case class Baz(x: Int) // 错误!
现在,我们来实现一个简单的DSL。通常,我们在计算货币运算时,如果不涉及到币种的转换,通常可以简单的用数学表达式计算,比如23 + 12
,在任何编程语言里都能做到。但是涉及到币种时,这种简单的表达式不满足要求了,需要做一下汇率转换才能进行计算,然而这样却不很直观。我们需要一种直观的运算,同时又要包含汇率转换的操作,比如像这样的表达式:23(USD) + 12(EUR)
,下面,我们就用scala的隐式转换实现这样的要求。
首先,我们定义一个关于货币的伴生类(class)和伴生对象(单例对象object)。定义它们的成员变量code,name以及object中对应的币种信息。关键代码如下:
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方法用于后面金钱的具体转换。
然后定义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)
用于币种转换,+
用于币种计算,此处仅仅实现了+
方法。
至此,我们实现了不同币种加法的运算。利用上面的代码,我们可以有三种不同的调用方法。
-
传统的方法调用
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隐式转换是一个非常强大的功能,但是实现起来很困难,dsl虽然使用方便,但是这样的“魔幻”代码理解起来还是费劲。