【问题标题】:What is the difference between mixins and inheritance?混合和继承有什么区别?
【发布时间】:2016-08-12 10:57:09
【问题描述】:

我试图在 scala 的上下文中理解 Mixins。特别是我想知道继承和 Mixins 概念之间的区别。

wiki 中 Mixin 的定义说:

mixin 类充当父类,包含所需的功能。然后子类可以继承或简单地重用此功能,但不能作为专门化的手段。通常,mixin 会将所需的功能导出到子类,而不会创建严格的、单一的“是”关系。这就是 mixins 和继承概念之间的重要区别,子类仍然可以继承父类的所有特性,但是,关于子类“是一种”父类的语义不必是必须应用

在上述定义中,我无法理解以粗体标记的陈述。这是什么意思

  1. 子类可以继承 mixin 中的功能,但不能作为特化的手段
  2. 在 mixins 中,子类继承了父类的所有特性,但是关于子类“成为一种”父类的语义不一定要应用。 - 孩子如何扩展父母而不一定是父母?有没有这样的例子。

【问题讨论】:

  • 在 Scala 中,将 Mixins 视为一种简洁的编译时转换,它将用额外的方法装饰某些类型。在这种情况下,虽然 Scala 为类型检查器跟踪“混合类型”,但方法定义本身被扁平化为实际类型,因此在 JVM 类中没有建立父子对象。继承通常与运行时多态方法解析相关联 - 但 mixin (很大程度上)是正交概念。 Java 8 接口默认方法也是 mixins。
  • 这个问题似乎是asked before in a broader context(虽然我对那里的答案并不完全满意)。
  • 在 [Scala 编程,第 3 版][1] 第 12 章 Traits 一书中有一个很长的答案描述器

标签: scala oop


【解决方案1】:

我不确定我是否正确理解了您的问题,但如果我理解了,您是在问一些东西如何继承,而没有真正与继承相同的含义。

然而,Mixins 不是继承——它实际上更像是动态地将一组方法添加到一个对象中。继承说“这个东西是另一种东西”,mixins 说,“这个对象有这个东西的一些特征。”您可以在用于声明 mixins 的关键字中看到这一点:trait

从 Scala 主页公然窃取示例:

abstract class Spacecraft {
  def engage(): Unit
}
trait CommandoBridge extends Spacecraft {
  def engage(): Unit = {
    for (_ <- 1 to 3)
      speedUp()
  }
  def speedUp(): Unit
}
trait PulseEngine extends Spacecraft {
  val maxPulse: Int
  var currentPulse: Int = 0
  def speedUp(): Unit = {
    if (currentPulse < maxPulse)
      currentPulse += 1
  }
}
class StarCruiser extends Spacecraft
                     with CommandoBridge
                     with PulseEngine {
  val maxPulse = 200
}

在这种情况下,StarCruiser 不是CommandoBridgePulseEngine;但是,它拥有它们,并获得了这些特征中定义的方法。它Spacecraft,正如您所见,因为它继承自该类。

值得一提的是,当 trait 扩展 class 时,如果你想制作 with 的特性,它必须扩展该类。例如,如果我有一个class Dog,我不能有一个Dog with PulseEngine,除非Dog 扩展Spacecraft。这样一来,就不太像添加方法了;但是,它仍然相似。

【讨论】:

  • > In this case, the StarCruiser isn't a CommandoBridge or PulseEngine 我真的不明白这是怎么回事,当我可以定义一个方法时,比如说launch(foo: CommandoBridge) 并这样称呼它launch(new StarCruiser())...不是通过的能力StarCruiser 的实例到期望 CommandoBridge 的方法的典型示例表明 StartCruiser 确实是 a CommandoBridge?
  • @dade 这更像是一种笨拙的语法情况(否则你怎么能从StarCruiser中得到CommandoBridge?)当然还有其他方法,但没有一个是特别漂亮,所以 Scala 设计者选择了他们认为最好的东西。如果我不得不猜测他们的推理,我会说这是因为他们不想在可能重载现有事物时添加额外的语法。
  • @dade 我真的不喜欢听起来我讨厌 Scala,但似乎 Scala 设计者试图从其他语言中纠正一些“有问题”的概念,但最终引入了更加复杂和模棱两可的概念那些。让开发人员拒绝就某一特定问题达成一致,从而造成更多混乱!
【解决方案2】:

trait(与类混合时称为 mixin)就像 Java 中的接口(尽管有很多 differences),您可以在其中添加额外的类的特征,而不必具有“是”关系。或者你可以说一般特征捆绑了可以被多个独立类使用的特性。

举个 Scala 库中的例子,Ordered[A] 是一个 trait,它提供了一些基本比较操作的实现(如 &lt;&lt;=&gt;&gt;=)到可以具有自然排序的数据。

例如,假设您有自己的类Number 和子类EvenNumberOddNumber,如下所示。

class Number(val num : Int) extends Ordered[Number] {
  override def compare(that : Number) = this.num - that.num
}

trait Half extends Number {
  def half() = num / 2
}

trait Increment extends Number {
  def increment() = num + 1
}

class EvenNumber(val evenNum : Int) extends Number(evenNum) with Half

class OddNumber(val oddNum : Int) extends Number(oddNum) with Increment

在上面的示例中,EvenNumberOddNumber 类与 Number 共享 关系,但 EvenNumberHalf 和 @987654337 都没有“是”关系@share “是”与 Increment 的关系。

另一个重要的一点是即使Number类使用extends Ordered语法,这意味着Number有一个隐式 是一个Ordered 的超类的关系,即Any

【讨论】:

  • “但 EvenNumber 与 Half 没有“是”关系,OddNumber 也没有与 Increment 共享“是”关系。 val h: Half = new EvenNumber(10) val i: Increment = new OddNumber(1) 这样编译执行完美,使得前面的语句不正确
【解决方案3】:

我认为它非常依赖于使用。 Scala 作为一种多范式语言,使其功能强大,但有时也令人困惑。 我认为以正确的方式使用 Mixins 非常强大。 应该使用 Mixins 来引入行为并减少 bolierplate。

Scala 中的 trait 可以有实现,并且很想扩展和使用它们。

特征可用于继承。它也可以称为 mixins,但在我看来这不是使用 mixin 行为的最佳方式。在这种情况下,您可以将特征视为 Java 抽象类。其中你得到的子类是超类(特征)的“类型”。

不过,Traits 也可以用作 proper mixins。现在使用特征作为mixin 取决于“你如何混合它”的实现。主要是问自己一个简单的问题。它是“特征的子类真的是特征的kind 还是特征行为中减少样板的行为”。 通常,最好通过将特征混合到对象而不是扩展特征来创建新类来实现。

例如考虑下面的例子:

    //All future versions of DAO will extend this
trait AbstractDAO{
  def getRecords:String
  def updateRecords(records:String):Unit
}
//One concrete version
trait concreteDAO extends AbstractDAO{
  override def getRecords={"Here are records"}
  override def updateRecords(records:String){
    println("Updated "+records)
  }
}
//One concrete version
trait concreteDAO1 extends AbstractDAO{
  override def getRecords={"Records returned from DAO2"}
  override def updateRecords(records:String){
    println("Updated via DAO2"+records)
  }
}
//This trait just defines dependencies (in this case an instance of AbstractDAO) and defines operations based over that
trait service{
  this:AbstractDAO =>

  def updateRecordsViaDAO(record:String)={  
  updateRecords(record) 
  }
  def getRecordsViaDAO={
  getRecords
  }
}


object DI extends App{
  val wiredObject = new service with concreteDAO //injecting concrete DAO to the service and calling methods
  wiredObject.updateRecords("RECORD1")
  println(wiredObject.getRecords)

  val wiredObject1 = new service with concreteDAO1
  wiredObject1.updateRecords("RECORD2")
  println(wiredObject1.getRecords)

}

concreteDAO 是扩展 AbstractDAO 的特征 - 这是继承

val wiredObject = new service with concreteDAO - 这是正确的混合行为 由于服务特征要求AbstractDAOmixin。无论如何,Service 扩展ConcreteDAO 是错误的,因为service 需要AbstractDAO,它不是AbstractDAO 的类型。 相反,您使用不同的 mixin 创建 service 的实例。

【讨论】:

  • “Scala 是一种多范式语言,使其功能强大,有时也有点令人困惑”我会说它使它变得复杂且不适合使用
  • 是的,我明白为什么有些人会这么认为。意见。
【解决方案4】:

mixin 和继承之间的区别在于语义级别。在语法级别上,它们都是相同的。

要混合一个特征,或者从一个特征继承,它们都使用extendswith,这是相同的语法。

在语义级别上,打算混入的特征通常与混合它的类没有is a 关系,这与打算被继承的特征不同。

对我来说,一个 trait 是 mixin 还是 parent 是非常主观的,这通常是混淆的根源。

【讨论】:

    【解决方案5】:

    我认为这是在谈论实际的类层次结构。例如,DogAnimal 的类型,如果它从类扩展(继承)。它可以在任何Animal 参数适用的地方使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-11
      • 2015-08-03
      • 1970-01-01
      • 2022-09-27
      • 1970-01-01
      • 2015-01-19
      • 2019-09-09
      相关资源
      最近更新 更多