Scala模式可以匹配匿名类吗?

afdcj2ne  于 8个月前  发布在  Scala
关注(0)|答案(3)|浏览(95)

假设我们有一个匿名类生成器:

// test class...
def fixture = new {
  val stubA = 42
  val stubB = 24
}

然后我想模式匹配和提取字段如下:

val { stubA } = fixture

不幸的是,Scala没有这个功能(TypeScript有)。
当然,使用case class可以做到这一点:

case class Fixture(stubA: Int, stubB: Int)
def fixture = Fixture(42, 24)

val Fixture(stubA, _)= fixture

显然,这太复杂了;我们只需要命名值。
我能以更简单的方式从匿名类进行模式匹配吗?

piah890a

piah890a1#

不,更进一步说,那个语法并不像你想的那样。匿名类的目的是覆盖一些定义良好的接口的行为。例如,我们可以创建一个实现Runnable并具有自定义run()行为的匿名类。但是他们不能真正有意义地向父级添加开始时不存在的字段或方法。

def fixture = new {
  val stubA = 42
  val stubB = 24
}

fixture的类型是Object。这是因为您创建了Object的匿名子类。您可以在fixture上调用Object,如toString()equals(...)。但是你不能写fixture.stubA,因为Object上不存在stubA
因此,您不仅不能对这些自定义的仅匿名字段和方法进行模式匹配,而且甚至不能使用常规语法调用它们。你只能使用Java反射来访问它们。[1]
底线是:如果你正在尝试用Scala编写JavaScript(或Typescript),你将面临一段艰难的时期。这两种语言有着非常不同的解决问题的方法,您刚刚找到了一个很好的例子。类型脚本代码中充斥着满足某些结构化接口的对象文字,但匿名类示例在Scala中并不常见。这就是为什么Scala为我们提供了case class es和合成apply构造函数以及所有其他简化类声明的方便方法。我们应该自由地宣布阶级。
[1]即使是用Java反射来做,也充满了地雷。你必须知道Scala如何编译示例变量。Java Reflection看到0参数方法int stubA()int stubB()。这些字段本身是私有的,所以它们可以被getDeclaredFields()检测到,但不能被getFields()检测到。底线是:真是一团糟。

am46iovg

am46iovg2#

如果你修改了你的代码,变成这样:

def fixture: Any { val stubA: Int; val stubB: Int } = new {
  val stubA = 42
  val stubB = 24
}

那么你就可以创造出一个精致的类型。实际上,Scala编译器总是在你做new Type { /* new definitions */ }之类的事情时创建精炼的类型,但根据版本的不同,它可能会将它向上转换为你精炼的类型,除非你显式地告诉它保留精炼。
你能把它和这个做个比对吗?
为了能够进行模式匹配,你必须有一个提取器(一个定义了unapply方法的值),它不能只是{}。例如,你可以这样写:

type Helper = Any { val stubA: Int; val stubB: Int } 
object Helper {
  def unapply(helper: Helper): Some[(Int, Int)] =
    Some((helper.stubA, helper.stubB))
}

def fixture: Helper = new {
  val stubA = 42
  val stubB = 24
}

val Helper(stubA, stubB) = fixture

但是,与使用一次性case class相比,这可能看起来工作量太大。
如果这个“匿名类生成器”也会生成一些别名和一些提取器,那么这个问题就可以解决了(尽管它仍然是非惯用的)。
最后,可以使用一些白盒宏/透明内联来生成这个unapply方法实现。但是,虽然它可以工作,但它不会与IDE非常合作。
所以,我仍然会使用单一用例类或没有模式匹配/解构的精炼类型(或评论者建议的元组)。因为它与现有的工具相配合。

vnzz0bqm

vnzz0bqm3#

我不知道你对你后面的片段有什么“复杂”的地方。你必须给你的班级命名?在我的书中,复杂的事情很难被称为“复杂性”,我会说,这是相当平凡的。
无论如何,从你的问题中我们并不清楚你到底想达到什么目的,但是你所写的内容让人想起了人们有时在scala中用来创建单元测试的模式:

class FooBarSpec extends FunSpec with MockitoSugar { 

   trait Fixture { 

      val fooMock = mock[Foo]
      val barMock = mock[Bar]

      when(fooMock.doFoo()).thenReturn("foo")
      when(barMock.doBar()).thenReturn("bar")

      val testMe = new FooBar(fooMock, barMock)
  }

  describe("FooBar") { 
    it ("does stuff") (new Fixture { 
    
     
     testMe.doStuff() mustBe "foobar"

     verify(fooMock).doFoo() 
     verify(barMock).doBar()
   })
 }
}

这个想法是你创建你的Fixture trait,在那里你为所有的测试定义你的存根和公共行为。然后,每个测试都发生在该fixture的匿名子类的构造函数体中。这样,在测试中,您就可以访问所有的公共定义,并且可以根据特定测试用例的需要覆盖它们。

相关问题