Scala:为什么使用 self-type 而不是 mixin 会产生 stackoverflow?
Scala: Why using self-type instead of mixin produces stackoverflow?
trait UserRepository {
def findByFirstName(firstName: String): Seq[User]
}
trait UserBusinessDelegate extends UserRepository {
abstract override def findByFirstName(firstName: String) = {
super.findByFirstName(firstName)
}
}
class MockUserRepository extends UserRepository {
override def findByFirstName(firstName: String) = {
// whatever
}
}
val userRepository = new MockUserRepository with UserBusinessDelegate
userRepository.findByFirstName("John") // OK
但是,如果我按如下方式更改 UserBusinessDelegate
:
trait UserBusinessDelegate {
self: UserRepository =>
override def findByFirstName(firstName: String): Seq[User] = {
self.findByFirstName(firstName) // requires explicit return type, thinks this is a recursive call
}
}
val userRepository = new MockUserRepository with UserBusinessDelegate
userRepository.findByFirstName("John") // Whosebug!!!
我了解可堆叠模式以及第一种情况的工作原理。我的问题是为什么第二个没有。
在第二个片段中,您有一个没有退出条件的递归调用:
override def findByFirstName(firstName: String): Seq[User] = {
self.findByFirstName(firstName)
}
这将始终从 UserBusinessDelegate
调用 findByFirstName
(因为您正在使用 self
,这基本上是说该对象在运行时会有这种行为,而不是父级将拥有它,因此我们应该调用父级的方法)每次调用都创建一个新的堆栈帧 -> 堆栈溢出。
在第二个片段中 UserBusinessDelegate
的 findByFirstName
将被调用,然后您使用 super
从中调用 MockUserRepository
的方法 -> 无递归 ->没有堆栈溢出。您可以查看 Scala's stackable trait pattern 了解更多信息。
@Edit:为了更清楚,在抛出 SO 异常的代码段中,不会调用 MockUserRepository
中的 findByFirstName
方法,因为您在 [=12= 中覆盖了它] 因此,使用 new MockUserRepository with UserBusinessDelegate
创建的匿名 class 将仅包含重写的方法,这就是 SO 的原因,清楚吗?
为什么您会假设来自 MockUserRepository
的方法会被调用?
@Edit2:如果没有 override
,代码将无法编译,因为 self: UserRepository =>
告诉编译器具有这种签名的方法在运行时已经存在,并且您不能有 2 个方法相同的签名。第一个例子之所以有效,是因为它是一个可堆叠的特征,这样的特征是动态绑定的,可以修改行为但必须在某些时候调用 super
(如果没有 abstract override
修饰符,这通常是不允许的,我真的建议阅读 link 我发布的有关可堆叠模式的文章)。
也许其他人知道一种方法,据我所知,除非您更改 UserBusinessDelegate
中的方法名称并删除 override
,否则无法调用 mock 方法,然后您可以调用 self.findByFirstName
并且它将调用来自 MockUserRepository
的方法。
trait UserRepository {
def findByFirstName(firstName: String): Seq[User]
}
trait UserBusinessDelegate extends UserRepository {
abstract override def findByFirstName(firstName: String) = {
super.findByFirstName(firstName)
}
}
class MockUserRepository extends UserRepository {
override def findByFirstName(firstName: String) = {
// whatever
}
}
val userRepository = new MockUserRepository with UserBusinessDelegate
userRepository.findByFirstName("John") // OK
但是,如果我按如下方式更改 UserBusinessDelegate
:
trait UserBusinessDelegate {
self: UserRepository =>
override def findByFirstName(firstName: String): Seq[User] = {
self.findByFirstName(firstName) // requires explicit return type, thinks this is a recursive call
}
}
val userRepository = new MockUserRepository with UserBusinessDelegate
userRepository.findByFirstName("John") // Whosebug!!!
我了解可堆叠模式以及第一种情况的工作原理。我的问题是为什么第二个没有。
在第二个片段中,您有一个没有退出条件的递归调用:
override def findByFirstName(firstName: String): Seq[User] = {
self.findByFirstName(firstName)
}
这将始终从 UserBusinessDelegate
调用 findByFirstName
(因为您正在使用 self
,这基本上是说该对象在运行时会有这种行为,而不是父级将拥有它,因此我们应该调用父级的方法)每次调用都创建一个新的堆栈帧 -> 堆栈溢出。
在第二个片段中 UserBusinessDelegate
的 findByFirstName
将被调用,然后您使用 super
从中调用 MockUserRepository
的方法 -> 无递归 ->没有堆栈溢出。您可以查看 Scala's stackable trait pattern 了解更多信息。
@Edit:为了更清楚,在抛出 SO 异常的代码段中,不会调用 MockUserRepository
中的 findByFirstName
方法,因为您在 [=12= 中覆盖了它] 因此,使用 new MockUserRepository with UserBusinessDelegate
创建的匿名 class 将仅包含重写的方法,这就是 SO 的原因,清楚吗?
为什么您会假设来自 MockUserRepository
的方法会被调用?
@Edit2:如果没有 override
,代码将无法编译,因为 self: UserRepository =>
告诉编译器具有这种签名的方法在运行时已经存在,并且您不能有 2 个方法相同的签名。第一个例子之所以有效,是因为它是一个可堆叠的特征,这样的特征是动态绑定的,可以修改行为但必须在某些时候调用 super
(如果没有 abstract override
修饰符,这通常是不允许的,我真的建议阅读 link 我发布的有关可堆叠模式的文章)。
也许其他人知道一种方法,据我所知,除非您更改 UserBusinessDelegate
中的方法名称并删除 override
,否则无法调用 mock 方法,然后您可以调用 self.findByFirstName
并且它将调用来自 MockUserRepository
的方法。