假设我有一个特征 Foo
用几种方法。我想创建一个扩展 Foo
的新特征但是“包装”每个方法调用,例如使用一些打印语句(实际上这会更复杂/我有几个不同的用例)。
trait Foo {
def bar(x: Int) = 2 * x
def baz(y: Int) = 3 * y
}
我可以通过覆盖每个方法手动执行此操作。但这似乎不必要地冗长(而且很容易调用错误的 super 方法):
object FooWrapped extends FooWrapped
trait FooWrapped extends Foo {
override def bar(x: Int) ={
println("call")
super.bar(x)
}
override def baz(y: Int) ={
println("call")
super.baz(y)
}
}
scala> FooWrapped.bar(3)
call
res3: Int = 6
我希望编写一个混合特征,我将能够与其他特征重用,并用作:
trait FooWrapped extends Foo with PrintCall
这样我就不必手动覆盖每个方法(mixin 会为我做这件事)。
是否可以在 Scala 中编写这样的混合特征?它会是什么样子?
最佳答案
更新 这是宏。由于准报价,这比我想象的要少得多。他们真棒。这段代码只做了一点,你可能需要改进它。它可能不考虑某些特殊情况。它还假设父类和它的方法都没有类型参数,它只包装给定类或特征的方法,而不是它的父方法,如果你有辅助构造函数等,它可能不起作用。但我希望它会给你关于如何根据您的特定需求做到这一点的想法,不幸的是,让它适用于所有情况,现在对我来说太大了。
object MacrosLogging {
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def log_wrap[T](): T = macro log_impl[T]
def log_impl[T : c.WeakTypeTag](c: blackbox.Context)(): c.Expr[T] = {
import c.universe._
val baseType = implicitly[c.WeakTypeTag[T]].tpe
val body = for {
member <- baseType.declarations if member.isMethod && member.name.decodedName.toString != "$init$"
method = member.asMethod
params = for {sym <- method.paramLists.flatten} yield q"""${sym.asTerm.name}: ${sym.typeSignature}"""
paramsCall = for {sym <- method.paramLists.flatten} yield sym.name
methodName = member.asTerm.name.toString
} yield {
q"""override def ${method.name}(..$params): ${method.returnType} = { println("Method " + $methodName + " was called"); super.${method.name}(..$paramsCall); }"""
}
c.Expr[T] {q""" { class A extends $baseType { ..$body }; new A } """}
}
}
如果你不想创建一个实例,但你只想为你的特征添加日志以便你可以进一步混合,你可以使用相对相同的代码来做到这一点,但使用宏天堂类型注释:http://docs.scala-lang.org/overviews/macros/annotations这些允许您标记您的类定义并在定义内执行修改
你可以用
Dynamic
做你想做的事情,但有一个问题——你不能把它做成原始类型,所以它不是混合。只有当类型检查失败时动态才开始工作,所以你不能混入真正的类型(或者我不知道怎么做)。真正的答案可能需要宏(正如@AlexeyRomanov 在评论中建议的那样),但我不知道如何写一个,也许我稍后会想出它。尚Dynamic
如果您不是在这里寻找 DI,可能对您有用 trait Foo {
def bar(x: Int) = 2 * x
def baz(y: Int) = 3 * y
}
import scala.reflect.runtime.{universe => ru}
import scala.language.dynamics
trait Wrapper[T] extends Dynamic {
val inner: T
def applyDynamic(name: String)(args: Any*)(implicit tt: ru.TypeTag[T], ct: ClassTag[T]) = {
val im = tt.mirror.reflect(inner)
val method = tt.tpe.decl(ru.TermName(name)).asMethod
println(method)
val mm = im.reflectMethod(method)
println(s"$name was called with $args")
mm.apply(args:_*)
}
}
class W extends Wrapper[Foo] {
override val inner: Foo = new Foo() {}
}
val w = new W // Cannot be casted to Foo
println(w.bar(5)) // Logs a call and then returns 10
您可以阅读更多关于
Dynamic
的信息这里:https://github.com/scala/scala/blob/2.12.x/src/library/scala/Dynamic.scala
关于scala - Mixin 来包装 Scala 特征的每个方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36244257/