Scala - 在特质中重写类方法

7

我是一个新手Scala程序员(之前从Ruby转过来)。

我对Scala中的“traits”概念很感兴趣(如果我理解正确的话,应该类似于Ruby中的模块)。

下面是一个使用案例。

假设我定义了一个名为User的类:

class User {
    def password() : String = "generating a password (default)"
}

假设我有一个trait SecurePasswords,我想要“覆盖”User类中定义的密码方法。

trait SecurePasswords {
    def password() : String = "generating a secure password (non-default)"
}

假设我希望它适用于User类的实例,而不是整个类本身。

val a = new User
val b = new User with SecurePasswords

a.password() # generating a password (default)
b.password() # generating a secure password (non-default)

现在这是我期望的理想输出,但是我遇到了不同的错误,例如“anonymous class inherits conflicting members ... (Note: this can be resolved declaring etc etc ...)”。 这可以在Scala中完成吗?还是我要求太多/做了一些非常奇怪的事情? 是否可以在没有任何额外类定义的情况下完成,如UserWithSecurePassword extends User
提前感谢大家! 附言:如果你想知道“为什么?”只需假设系统将包含许多需要密码(可能是安全密码)的实体,因此该特征可以在许多地方使用。
6个回答

12

这两个方法定义之间的冲突是因为你没有将它们定义为“同一个方法”。你所做的只是巧合地让它们拥有相同的名称、参数和返回类型。

要使它们真正成为同一个方法,以便其中一个可以覆盖另一个,请在同一位置定义它们:

trait HasPassword {
  def password(): String
}

class User extends HasPassword {
  override def password(): String = "generating a password (default)"
}

trait SecurePassword extends HasPassword {
  override def password(): String = "generating a password (non-default)"
}

new User with SecurePassword

通过在特质HasPassword中进行定义,然后在UserSecurePassword中进行重写(而不是二次生成或鸭子类型),Scala能够理解这实际上是重新定义的同一方法。因此,在混入SecurePassword时,它可以覆盖User中的password()方法。


5

除了我之前的回答外,另一种完全不同的方法是将密码函数传递到User类中,而不是使用特性:

  class User(pw: ()=>String=default) {
    def password = pw
  }

  val default = () => "generating a password (default)"
  val secure = () => "generating a secure password (non-default)"

  val a = new User()
  val b = new User(secure)

  a.password() // generating a password (default)
  b.password() // generating a secure password (non-default)

如图所示,您可以使用默认参数来避免在默认情况下指定密码函数。

1
经过更仔细的研究,我认为这个解决方案比使用特征更简单、更优雅。 - josiah

1
您可以按以下方式为每个实例提供不同的密码行为 - 但是您需要在每种情况下提供明确的特征(默认或安全):
  abstract class User {
    def password(): String
  }

  trait SecurePasswords {
    def password(): String = "generating a secure password (non-default)"
  }

  trait DefaultPasswords {
    def password(): String = "generating a password (default)"
  }

  val a = new User with DefaultPasswords
  val b = new User with SecurePasswords

  a.password() // generating a password (default)
  b.password() // generating a secure password (non-default)

更新:然而,我认为丹·盖茨的答案可能更接近您最初提出的问题。

1

我不确定这个用例是什么。为什么不让用户在Ruby术语中混入“安全密码”特性,并在其类定义内重写密码呢?

如果你来自Ruby,可能更难以理解,但Scala是一种编译型语言,动态/即时更改类定义通常不是一个好主意。将类型系统视为测试代码的一种方式。你推迟的代码解释到运行时越多,你的代码就越不易测试/不安全。这是Scala / Java / Haskell 等编译类型语言的优势之一 - 类型系统可以在编译时捕获很多错误。利用这一点,而不要与之对抗。

我会研究如何使用隐式参数及其如何与特质相关/使用。

此外,如果你试图实现某种适配器模式,没有更广泛的上下文,很难知道,但是这个链接可能对你有用。

http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html


1

我遇到了类似的挑战,稍有不同的是我无法控制User类 - 即它是从第三方库中引入的。一种“自身类型”方法完全有效:

class User {
  def password() : String = "generating a password (default)"
}

trait SecurePasswords { this: User =>
  override def password() : String = "generating a secure password (non-default)"
}

val a = new User
val b = new User with SecurePasswords

a.password() // generating a password (default)
b.password() // generating a secure password (non-default)

0

注意:关于错误信息,存在一个问题128“从Java默认方法继承冲突成员时没有歧义错误”,该问题正在等待解决。

在Scala 2.12.x中应该已经解决了提交3a3688f64

SD-128修复default方法的覆盖检查

对于默认方法,继承两个冲突成员的检查是错误的,导致缺少错误消息。

当覆盖默认方法时,我们也没有发出“需要`override'修饰符”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接