Scala元编程:实现lombok.Data

栏目: Scala · 发布时间: 5年前

内容简介:如果你读完了所以,我建议你不要阅读本文,直接自己尝试。我们希望通过

如果你读完了 《Scala元编程:伊甸园初窥》 ,理论上你已经具备实现 lombok.Data 的能力了。

所以,我建议你不要阅读本文,直接自己尝试。

定义 lombok.Data 的 Scala 版

@data
class A {
  var x: Int = _
  var y: String = _
}
复制代码

我们希望通过 @data 这个注释,自动生成如下代码:

class A {
  var x: Int = _
  var y: String = _

  def getX(): Int = x
  def setX(paramX: Int): Unit = { x = paramX }
  def getY(): Int = x
  def setY(paramY: String): Unit = { y = paramY }
}
复制代码

为什么要生成这样的代码呢?就个人而言,我是为了在Spring Boot和Scala混合编写的项目中无缝地使用MyBatis。在使用 Java 时,我们可以很方便地使用lombok.Data生成我们所需的Getter和Setter。而在Scala生态中,已经有了case class,这种写法其实对于Pure Scala的 程序员 来说,是相当离经叛道的。

在用Scala和Java混合编程的时候,我觉得其实最重要的一点是 选择 。实现一个功能的方式可能有100(二进制哦)种,但是最适合的方式,永远只有一种。

我选择了MyBatis,不采用元编程的手段(其实这段时间我刚刚学会),我是这样做的:

import scala.beans.BeanProperty
class A {
  @BeanProperty var x: Int = _
  @BeanProperty var y: String = _
}
复制代码

用Vim列编辑,其实也还好。但是我内心其实一直在对自己说: DO NOT REPEAT YOURSELF

参考实现

// ...
      annottees.map(_.tree).toList match {
        case q"""
              class $name {
                ..$vars
              }
              """ :: Nil =>

          // Generate the Getter and Setter from VarDefs
          val beanMethods = vars.collect {
            case q"$mods var $name: $tpt = $expr" =>
              val getName = TermName("get" + name.encodedName.toString.capitalize)
              val setName = TermName("set" + name.encodedName.toString.capitalize)
              println(getName)
              val ident = Ident(name)
              List (
                q"def $getName: $tpt = $ident",
                q"def $setName(paramX: $tpt): Unit = { $ident = paramX }"
              )
          }.flatten

          // Insert the generated Getter and Setter
          q"""
             class $name {
               ..$vars
               ..$beanMethods
             }
           """
        case _ =>
          throw new Exception("Macro Error")
      }
    }
    // ...
复制代码

单元测试

上一篇元编程相关的文章实际上主要是为了强调构建,所以我贴了两次构建定义的代码。

test("generate setter and getter") {
    @data
    class A {
      var x: Int = _
      var y: String = _
    }

    val a = new A
    a.setX(12)
    assert(a.getX === 12)
    a.setY("Hello")
    assert(a.getY === "Hello")
}
复制代码

lombok在IntelliJ Idea中有专门的插件,去处理Idea无法定位到的程序自动生成的Getter和Setter。如果我们只是为了让MyBatis能够识别和使用,我们就没有必要再去为我们的Scala版 lombok.Data 专门定制一个插件。在我们自己的代码中,没有必要使用Getter和Setter,因为Scala在语言级别已经支持了(如果你一脸懵逼,我建议你先阅读一下《快学Scala》和《Scala实用指南》的样章)。

test("handle operator in the name") {
    @data
    class B {
      var op_+ : Int = _
    }

    val b = new B
    b.setOp_+(42)
    assert(b.getOp_+ === 42)
  }
复制代码

这个地方也涉及到了一个Scala相关的知识点,我记得在《快学Scala》中看到过。在参考实现中,与这个单测相关的代码是这两行:

val getName = TermName("get" + name.encodedName.toString.capitalize)
val setName = TermName("set" + name.encodedName.toString.capitalize)
复制代码

这里就不展开了。

单元测试的风格

Scala项目的单测,我一直用ScalaTest,但是ScalaTest官网的例子给的是FlatSpec:

"A Stack" should "pop values in last-in-first-out order" in {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    stack.pop() should be (2)
    stack.pop() should be (1)
  }
复制代码

大概是这种代码风格。我们需要在两个地方填入一些信息,有点烦人。所以,我推荐FunSuite这种风格:

test("A Stack pop values in last-in-first-out order"){
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    stack.pop() should be (2)
    stack.pop() should be (1)
  }
复制代码

我只需要填一句话,不需要考虑主语,对IDE也更加友好。

编译期与运行时

这是元编程里面两个特别重要的概念。广义上讲,实际上,这些概念都在试图提醒我们,注意一下是谁(那台机器上的那个进程)在运行我们的代码。

提交代码的时候,不小心忘记把调试用的 println(getName) 清理掉,索性就不去清理了。

使用sbt去运行我们的单元测试:

$ sbt
sbt:paradise-study> test
// 编译期开始
[info] Compiling 1 Scala source to $HOME/github/paradise-study/lombok/target/scala-2.12/test-classes ...
getX
getY
getOp_$plus
[info] Done compiling.
// 编译期结束
// 运行
[info] DataSuite:
[info] - generate setter and getter
[info] - handle operator in the name
[info] Run completed in 437 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 4 s, completed 2019-1-1 16:15:43
复制代码

以上所述就是小编给大家介绍的《Scala元编程:实现lombok.Data》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro HTML5 and CSS3 Design Patterns

Pro HTML5 and CSS3 Design Patterns

Michael Bowers / Apress / 2011-11-15 / GBP 35.50

Pro HTML5 and CSS3 Design Patterns is a reference book and a cookbook on how to style web pages using CSS3 and HTML5. It contains 350 ready--to--use patterns (CSS3 and HTML5 code snippets) that you ca......一起来看看 《Pro HTML5 and CSS3 Design Patterns》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具