1

我正在与如下所示的外部 Java API 交互:

val obj: SomeBigJavaObj = {
  val _obj = new SomeBigJavaObj(p1, p2)
  _obj.setFoo(p3)
  _obj.setBar(p4)
  val somethingElse = {
    val _obj2 = new SomethingElse(p5)
    _obj2.setBar(p6)
    _obj2
   }
  _obj.setSomethingElse(somethingElse)
  _obj
}

基本上,Java API 公开了一堆.setXXXX返回void和设置某些东西的方法。我无法控制这些外部 POJO。

因此,我想编写一个流利的buildScala 宏来检查对象并.withXXXX()为每个 void setXXXX()返回的方法创建一个构建器模式类型方法this

val obj: SomeBigJavaObj =
  build(new SomeBigJavaObj(p1, p2))
    .withFoo(p3)
    .withBar(p4)
    .withSomethingElse(
       build(new SomethingElse(p5))
         .withBar(p6)
         .result()
    )
    .result()

这可能吗?我知道我无法使用宏生成新的顶级对象,def因此我对其他具有类似人体工程学的建议持开放态度。

4

2 回答 2

6

使用宏并不复杂;只是对 IDE 不友好(例如:代码完成;...);

//edit 1 : 支持多个参数

实体:

public class Hello {
  public int    a;
  public String b;


  public void setA(int a) {
    this.a = a;
  }

  public void setB(String b) {
    this.b = b;
  }

  public void setAB(int a , String b){
    this.a = a;
    this.b = b;
  }
}

宏代码:import scala.language.experimental.macros import scala.reflect.macros.whitebox

trait BuildWrap[T] {
  def result(): T
}

object BuildWrap {
  def build[T](t: T): Any = macro BuildWrapImpl.impl[T]
}

class BuildWrapImpl(val c: whitebox.Context) {

  import c.universe._

  def impl[T: c.WeakTypeTag](t: c.Expr[T]) = {
    val tpe = c.weakTypeOf[T]
    //get all set member
    val setMembers = tpe.members
      .filter(_.isMethod)
      .filter(_.name.toString.startsWith("set"))
      .map(_.asMethod)
      .toList
    // temp value ;
    val valueName = TermName("valueName")

    val buildMethods = setMembers.map { member =>
      if (member.paramLists.length > 1)
        c.abort(c.enclosingPosition,"do not support Currying")

      val params = member.paramLists.head
      val paramsDef = params.map(e=>q"${e.name.toTermName} : ${e.typeSignature}")
      val paramsName = params.map(_.name)

      val fieldName = member.name.toString.drop(3)//drop set
      val buildFuncName = TermName(s"with$fieldName")
      q"def $buildFuncName(..$paramsDef ) = {$valueName.${member.name}(..$paramsName);this} "
    }


    val result =
      q"""new BuildWrap[$tpe] {
        private val $valueName = $t
        ..${buildMethods}
        def result() = $valueName

       }"""

    // debug
    println(showCode(result))
    result
  }
}

测试代码:

val hello1: Hello = BuildWrap.build(new Hello).withA(1).withB("b").result()
assert(hello1.a == 1)
assert(hello1.b == "b")

val hello2: Hello = BuildWrap.build(new Hello).withAB(1, "b").result()
assert(hello2.a == 1)
assert(hello2.b == "b")
于 2018-03-13T09:30:33.127 回答
0

不是解决方案,只是一个非常初步的模型

 +---------------------------------------------------------+
 |                                                         |
 |                 D I S C L A I M E R                     |
 |                                                         |
 |  This is a mock-up. It is not type-safe. It relies on   |
 |  runtime   reflection   (even  worse:  it  relies  on   |
 |  Java-reflection!). Do  not  use  this in production.   |
 |                                                         |
 |  If you can come up with a type-safe solution, I will   |
 |  definitely take a look at it and upvote your answer.   |
 |                                                         |
 +---------------------------------------------------------+

您已经明确说过类型安全是必须的,因此下面的代码不能算作解决方案。但是,在进一步调查之前,也许您想尝试一个纯粹的基于运行时反射的实现,以更好地理解需求。这是一个非常快速和肮脏的模型实现:

import scala.language.dynamics

class DynamicBuilder[X](underConstruction: X) extends Dynamic {
  val clazz = underConstruction.getClass
  def applyDynamic(name: String)(arg: Any): DynamicBuilder[X] = {
    if (name.startsWith("with")) {
      val propertyName = name.drop(4)
      val setterName = "set" + propertyName
      clazz.getDeclaredMethods().
        find(_.getName == setterName).
        fold(throw new IllegalArgumentException("No method " + setterName)) {
          m => 
          m.invoke(underConstruction, arg.asInstanceOf[java.lang.Object])
          this
        }
    } else {
      throw new IllegalArgumentException("Expected 'result' or 'withXYZ'")
    }
  }
  def result(): X = underConstruction
}

object DynamicBuilder {
  def build[A](a: A) = new DynamicBuilder[A](a)
}

一旦build- 方法被导入

import DynamicBuilder.build

并且对应 POJO 的类的定义在范围内

class SomethingElse(val p5: String) {
  var bar: String = _
  def setBar(s: String): Unit = { bar = s }

  override def toString = s"SomethingElse[p5 = $p5, bar = $bar]"
}

class SomeBigJavaObj(val p1: Float, val p2: Double) {
  var foo: Int = 0
  var bar: String = _
  var sthElse: SomethingElse = _

  def setFoo(i: Int): Unit = { foo = i }
  def setBar(s: String): Unit = { bar = s }
  def setSomethingElse(s: SomethingElse): Unit = { sthElse = s }

  override def toString: String = 
    s"""|SomeBigJavaObj[
        |  p1 = $p1, p2 = $p2, 
        |  foo = $foo, bar = $bar, 
        |  sthElse = $sthElse
        |]""".stripMargin
}

并且还定义了您示例中的所有必需变量p1,...p6

val p1 = 3.1415f
val p2 = 12345678d
val p3 = 42
val p4 = "BAR"
val p5 = "P5"
val p6 = "b-a-r"

您可以完全使用问题中的语法:

val obj: SomeBigJavaObj =
  build(new SomeBigJavaObj(p1, p2))
    .withFoo(p3)
    .withBar(p4)
    .withSomethingElse(
       build(new SomethingElse(p5))
         .withBar(p6)
         .result()
    )
    .result()

结果如下所示:

println(obj)
// Output:
// SomeBigJavaObj[
//   p1 = 3.1415, p2 = 1.2345678E7, 
//   foo = 42, bar = BAR, 
//   sthElse = SomethingElse[p5 = P5, bar = b-a-r]
// ]

目前,这个想法只是为了看看当你尝试将它与一个更现实的例子一起使用时它失败的严重程度。事实证明,一切都稍微复杂一点:

  • 也许有些二传手是通用的
  • 也许他们中的一些人使用带有 Java 奇怪的调用站点差异的 Java 通配符
  • 也许除了 setter 之外,还有其他一些将多个参数作为可变参数的方法
  • 也许有重载的 setter 名称相同但参数类型不同。
  • 等等

我知道这不是一个解决方案,但是,我希望这可能有助于作为额外的可行性检查,并且在投入更多时间和精力于类型安全宏之前,它可能有助于使要求更加精确基于的解决方案。

如果这大致符合您的要求,我可能会考虑更新答案。如果这根本没有帮助,我将删除答案。

于 2018-03-11T11:54:04.147 回答