Dynamic
从Scala 2.10 开始,增加了Dynamic类型,所有直接或间接的继承自 Dynamic 的类,都可以实现。
Dynamic 继承自 Any,它的源代码中什么都没有,只有一句话。
traitDynamic extends Any
@meta.languageFeature("extension of type scala.Dynamic", enableRequired = true)
sealed trait dynamics
object dynamics extends dynamics
参考:http://www.scala-lang.org/api/current/scala/Dynamic.html
A marker trait that enables dynamic invocations. Instances x
of this trait allow method invocations x.meth(args)
for arbitrary method names meth
and argument lists args
as well as field accesses x.field
for arbitrary field names field
.
按照官方的描述,Dynamic 是一个运行动态执行的 trait 标识,它没有成员,具体的实现由编译器嵌入,它可以动态的执行任意名字的方法或参数。这个怎么理解呢,我姑且认为它是动态产生方法和字段吧。
要想使用Dynamic ,需要打开编译器选项 -language:dynamics 或者 导入包:
Scala 思考再三还是加入了 Dynamic Types,这个特性在 Scala 2.9 中是试验性的,必须用 -Xexperimental 进行开启,到了 Scala 2.10.0 中,只有代码中 import scala.language.dynamics 就可用了,或是编译时加 -language:dynamics 选项。
虽然 Scala 2.10.0 加进了 Dynamic Types 特性,但 Scala 仍然是静态类型的语言,因为在编译器同样会检查多出来的类型。
有了 Dynamic Types 之后,Scala 又可更 DSL 了,方法名的动态上可以让它随时包括深刻的业务含义。相比 Java 的 DSL 的能力就太逊了,我们几乎无法在 Java 面前提 DSL 这回事。
import scala.language.dynamics
Dynamic 的操作都会经过下面四个方法
- selectDynamic:创建字段或方法
- updateDynamic:更新字段或方法
- applyDynamic:执行方法,可以带参数
- applyDynamicNamed:执行方法,参数可以指定名称
同样是 apply 开头,所以这个方法是对 applyDynamic 方法的补充,即使没有 applyDynamicNamed,单用 applyDynamic 也能达成我们的要求。applyDynamicNamed 只是让你用命名参数调用时方便,也就是像
p.showInfo(screenName="Unmi", email="fantasia@sina.com") 这样用命名参数的方式来调用动态方法时会调用 updateDynamicNamed 方法。有了这个方法在命名传递参数就方便处理 key/value 值。
这四个方法在一个动态类中只能分别定义一个版本,否则会产生二义性,这和普通方法的重载不一样的。柯里化后的函数第二个括号中的参数可根据实际调用来定义,定义成 (args: Any*) 可包打天下。
代码示例
import scala.collection.mutable
import scala.language.dynamics
class DynamicPerson extends Dynamic {
//定义一个方法类型 CallFun,它接收 Int 类型参数,并返回 String 类型 ,这个有点像 C# 中的 delegate
type CallFun = Int => String
//Map对象,存放属性
private val fields = mutable.HashMap.empty[String, Any].withDefault {
key => throw new NoSuchFieldError(key)
}
//Map对象,存放方法对象
private val functions = mutable.HashMap.empty[String, CallFun].withDefault {
key => throw new NoSuchFieldError(key)
}
//选取对象
def selectDynamic(key: String) = fields(key)
/**
* 更新key对应的value
* 这里做了一个判断,如果key以call字符串开头,我们认为是args是CallFun类型
*/
def updateDynamic(key: String)(args: Any): Unit = {
args match {
case func if key.startsWith("call") => functions(key) = func.asInstanceOf[CallFun]
case _ => fields(key) = args
}
}
//这个就是用来动态执行方法的
def applyDynamic(key: String)(arg: Int) = {
println(functions(key)(arg))
}
def applyDynamicNamed(method: String)(args: (String, Any)*) {
println(s"applyDynamicNamed->$method called, args: $args")
for ((key, value) <- args) {
println(s"key: $key, value: $value")
}
}
}
object SimpleDemo {
def main(args: Array[String]) {
val person = new DynamicPerson()
//设置Name属性
person.Name = "Mike"
//定义一个 call 方法
person.callAge = (age: Int) => s"${person.Name} 今年 $age 岁"
//通过 applyDynamic 执行 call 方法
person.callAge(80) //这句代码将打印出:"Mike 今年 80 岁"
person.showInfo(screenName="Unmi", email="fantasia@sina.com")
}
}