Scala 覆写类成员和trait成员

Scala 覆写类成员和trait成员

我们可以在类中及trait 中声明抽象成员,包括抽象字段、抽象方法和抽象类型。在创建实例前,继承类或trait 必须定义这些抽象成员。大多数面向对象语言都支持抽象方法,其中某些语言还支持抽象字段和抽象类型。
假如你需要对Scala 的某一具体成员进行覆写,覆写该成员时必须使用override 关键字。假如某一子类型定义(也可以说“覆写”)了抽象成员,override 关键字是可省略的。反过来说,如果并未覆写某一成员但却使用了override 关键字,这会导致错误。

强制使用override 关键字会带来下列好处。
• 有些成员本应对其他成员进行覆写,而使用override 关键字能够捕获这些成员的拼写错误。假如这些成员未覆写任何成员,编译器便会抛出错误。
• 向基类中添加新的成员时,由于基类开发人员对继承类并不太了解,这就可能会出现新添的成员名与继承类中某一已经存在的成员名称冲突,而Scala 能够捕获这一细微的错误。也就是说,我们不希望继承类中的成员对基类成员进行覆写,而由于该继承类成员未提供override 关键字,当引入这个新的基类成员时,编译器便会抛出错误。
• 由于必须添加这一关键字,这有助于提醒你考虑到底哪些成员应该被覆写。

Java 提供了@Override 对方法进行注解,你可以选择是否添加该注解。尽管该注解能够辅助用户捕获拼写错误,但是由于注解是可选的,因此它无法帮助用户捕获上面第二项所描述的细微错误。
在实现抽象方法时,能否选择性地使用override 关键字呢?我们一起听听赞同和反对的声音。
除了之前提出的几点之外,赞同使用override 关键字的理由还包括如下2 点。
• 使用override 关键字能提醒读者,定义在父类中的某一成员已经被实现了(也有可能被覆写了)。
• 假如父类移除了已经在某一子类中定义了的抽象成员,系统将会报错。
而反对使用override 关键字的理由如下。
• 捕获拼写错误其实并没必要。未定义“覆写”也意味着当前仍然存在未定义的成员,因此继承类(或该类的其他具体类)无法通过编译。
• 在代码库的演变过程中,假如维护父类某一抽象成员的开发人员决定将该抽象成员改为具体成员,这一变化在编译子类时无法被注意到。现在子类应该调用该方法的父类实现吗?编译器会默默地使用子类方法覆写父类新定义的实现。

避免覆写具体成员

换句话说,抽象成员到底应不应该使用override 关键字呢?我们很难给出答案。这一问题也引入了更多的问题:你是否应该覆写一个具体方法呢?正确的回答是,大多数情况下,你不应该这样做。
父类型和子类型的关系就好比一纸契约,我们需要费心确保子类没有破坏父类型所指定的实现行为。
覆写具体成员时,很容易破坏掉这纸契约。覆写foo 方法时是否应该调用super.foo 方法呢?如果调用了super.foo 方法,子类的实现方法应该什么时候调用该方法呢?当然,正确的做法取决于具体的场景。
著名的“四人组”所编写的《设计模式》一书中描述的模版方法模式(template methodpattern)构造了一个更为牢固的契约。在该模式中,父类提供了某一方法的具体实现,并以此定义了某一行为的主要轮廓。而需要使用多态行为时,该方法也会调用一些protected 抽象方法。在此之后,子类型则只需要实现proteced 抽象方法即可。
下面我们给出这样一个示例,说明供美国公司使用的工资计算器的粗略实现。

case class Address(city: String, state: String, zip: String)

case class Employee(name: String, salary: Double, address: Address)

abstract class Payroll {
  def netPay(employee: Employee): Double = { // netPay 方法应用了模版方法模式。该方法定义了计算工资的协议,并将像这类每年都会变化的具体细节委托给抽象方法处理。
    val fedTaxes = calcFedTaxes(employee.salary)
    val stateTaxes = calcStateTaxes(employee.salary, employee.address)
    employee.salary - fedTaxes -stateTaxes
  }
  def calcFedTaxes(salary: Double): Double //计算美国联邦税。
  def calcStateTaxes(salary: Double, address: Address): Double //计算州税
}
object Payroll2014 extends Payroll {
  val stateRate = Map(
    "XX" -> 0.05,
    "YY" -> 0.03,
    "ZZ" -> 0.0)
  def calcFedTaxes(salary: Double): Double = salary * 0.25 // 定义父类中抽象方法的具体实现
  def calcStateTaxes(salary: Double, address: Address): Double = {
    // Assume the address.state is valid; it's found in the map!
    salary * stateRate(address.state)
  }
}

请注意,本实现中未出现override 关键字。
对于“不要覆写父类具体方法”这条规则,我能想到两个例外。某一方法的父类实现对于子类而言确实没有用处,这是其一。例如toString、equals 和hashCode 方法。不幸的是,覆写这些无用的默认方法非常普遍,以至于我们都满足于对具体方法进行覆写。
除非具体方法是toString 这样的无用方法,否则请不要覆写具体方法。除非你确实是在覆写具体方法,否则不要使用override 关键字。

假如某一声明中包含final 关键字,那么Scala 不允许覆写该声明。

猜你喜欢

转载自blog.csdn.net/u014646662/article/details/84140992
今日推荐