scala - Mixin 来包装 Scala 特征的每个方法

标签 scala decorator traits

假设我有一个特征 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/

相关文章:

scala - IntelliJ IDEA Scala 插件的语法突出显示以红色显示 Scala 包

mongodb - Scala 中是否存在用于 MongoDB "schema"升级的良好框架?

python - 类级装饰器

generics - Rust 泛型局部变量

scala - 如何在加特林的两个场景之间传递值?

Python 元类与类装饰器

reactjs - 使用 react-app-rewired : server start error 升级到 create-react-app 版本 4.0

closures - 为闭包类型别名实现特征

python - 将嵌套的 HasTraits 属性添加到 TraitsUI TView

scala - EsHadoopIllegalArgumentException:无法检测ES版本Spark-ElasticSearch示例