關於Scala多重繼承的菱形問題

在Scala中的trait中引入了混入的概念,即Mixin of trait。 java

什麼是混入(mixin)

    可能翻譯不許確,有人也稱之爲混入類(mixins),混入是一種組合的抽象類,主要用於多繼承上下文中爲一個類添加多個服務,多重繼承將多個 mixin 組合成一個類。例如,若是你有一個類表示「馬」,你能夠實例化這個類來建立一個「馬」的實例,而後經過繼承像「車庫」和「花園」來擴展它,使用 Scala 的寫法就是: 算法

val myHouse = new House with Garage with Garden
    從 mixin 繼承並非一個特定的規範,這只是用來將各類功能添加到已有類的方法。在 OOP 中,有了mixin,你就有經過它來提高類的可讀性。
object Test {
  def main(args: Array[String]): unit = {
    class Iter extends StringIterator(args(0))
    with RichIterator[char]
    val iter = new Iter
    iter foreach System.out.println
  }
}
    如Iter類經過RichIterator和StringIterator這兩個父類混入構成,第一個父類仍然稱爲超類(superclass),第二個父類則稱爲混入類(mixin)。
    在介紹Scala混入機制以前,先說明多重繼承的菱形問題。

多重繼承的鑽石問題

    又叫菱形問題(有時叫作「致命的死鑽石」deadly diamond of death),描述的是B和C繼承自A,D繼承自B和C,若是A有一個方法被B和C重載,而D不對其重載,那麼D應該實現誰的方法,B仍是C?
                                 A
                               ↗ ↖
                              B     C
                               ↖ ↗
                                 D
    在C++中是經過虛基類virtual實現,並按照深度優先,從左到右的順序遍歷調用,雖然解決了菱形問題,但因爲C++包含指針,繼承關係上容易形成結構混亂的狀況。
    那麼,Scala是如何處理這個菱形問題的。在這以前,首先了解Scala中幾個概念。 編程

JVM上的trait類

    編程語言性能的瓶頸關鍵在編譯器,Scala也不例外,Scala編譯器是直接將Scala編譯成.class文件的。而生成的class文件則取決於你如何定義。當你定義一個只包含方法聲明而不包含方法體的trait類,他會編譯成一個Java接口。你能夠使用javap –c <class file name>查看。例如,trait Empty{def e:Int}會產生以下的類: app

public interface Empty{
  public abstract int e();
}
    若是trait聲明瞭具體的方法或代碼,Scala會生成兩個類:一個接口類和一個包含代碼的新類。當一個類繼承這個trait時,trait中聲明的變量將被複制到這個類文件中,而定義在trait中的方法做爲這個繼承類的外觀模式的方法。這個類調用這個方法時,將調用新類中的對應方法。
    瞭解了這麼多,下面說說Scala處理菱形問題的機制。

Scala多重繼承機制

    Scala 的基於混入的類構成(mixin class composition)體系是線性混入構成(linearmixin compostion)和對稱的混入模塊(mixin modules),以及traits這三者的融合。 ssh

    Scala是經過類的全序化(Class Linearization),或稱做類的線性化。線性化指出一個類的祖先類是一條線性路徑的,包括超類(superclass)和特性(traits)。它經過兩步來處理方法調用的問題:
    ① 使用右孩子優先的深度優先遍歷搜索(right-first,depth-first search)算法進行搜索。
    ② 遍歷獲得的結構層次中,保留最後一個元素,其他刪除。

    線性混入,便是指使用右孩子優先的深度優先遍歷搜索算法,列出層次結構(Scala class hierarchy),所以Scala多重繼承的混入類中,若是包含有混入類(Mixins,或稱爲混入組合),則多重繼承中老是選擇最右邊的(right-mostly)的實現方法。分析以下代碼: 編程語言

package net.scala.chapter3.test

import ***

/**
 * @author Barudisshu
 */
@FixMethodOrder(MethodSorters.JVM)
class TestMixin extends AssertionsForJUnit with LazyLogging {

  @Test def test() {
    val mixin = Mixin("jijiang")
    mixin.foo("jijiang: ")
  }
}

trait jijiang {
  def foo(msg: String) = println(msg)
}

trait mama extends jijiang {
  val str1 = "mama: "
  override def foo(msg: String) = println(str1.concat(msg))
}

trait papa extends jijiang {
  val str2 = "papa: "
  override def foo(msg: String) = println(str2 + msg)
}

class Mixin private(msg: String) extends jijiang {
  def this() = this("mixin")
}

object Mixin {
  // 若是包含菱形問題,則只執行最右邊的
  def apply(msg: String) = new Mixin(msg) with papa with mama
}

    這裏,實際輸出結果爲mama: jijiang: ,說明,trait papa並無執行,trait mama符合最右(最後)的深度優先遍歷結果。 ide

惰性求值問題

    惰性求值能夠說是函數式語言中不可避免的事實,慶幸的是,Scala是一門強靜態的語言,在執行效率上比動態語言要高效些。 函數

    在上述例子中,println方法不是jijiang、papa、mama任何一方的成員,所以,面對惰性求值問題時老是會執行,可是,若是改成下面這樣: post

trait jijiang {
  def foo(msg: String) = println(msg)
}

trait mama extends jijiang {
  val str1 = "mama: "
  override def foo(msg: String) = super.foo(str1.concat(msg))
}

trait papa extends jijiang {
  val str2 = "papa: "
  override def foo(msg: String) = super.foo(str2 + msg)
}

class Mixin private(msg: String) extends jijiang {
  def this() = this("mixin")
}

object Mixin {
  // 若是包含菱形問題,則只執行最右邊的
  def apply(msg: String) = new Mixin(msg) with papa with mama

  // 因爲Scala的惰性求值問題,包含多重繼承的父類中的成員變量名稱應該不同,不然形成編譯錯誤

}

    輸入結果是什麼?答案是papa: mama: jijiang: 可能會奇怪,難道使用了super關鍵字就能夠避免菱形問題?不是,輸出結果順序仍然是按照深度優先遍歷的順序,但有所不一樣。由於,在Scala中,super關鍵字是動態調用的,這意味着super中的方法並非立刻執行,而是在真正被調用時執行,即惰性求值。因此上述代碼中,會按照jijiang<-papa<-mama的順序組裝字符串,即str2+msg<-str1.concat(msg),而後打印輸出pirntln(str1.concat(str2+msg)),所以,在Scala中就能夠達到屏蔽菱形問題的做用。 性能

    由於在new Mixin(msg) with papa with mama中,mama的super是papa,因此,下面代碼是等價的:

trait jijiang {
  def foo(msg: String) = println(msg)
}

trait mama extends jijiang {
  val str1 = "mama: "
  override def foo(msg: String) = super.foo(str1.concat(msg))
}

trait papa extends jijiang {
  val str2 = "papa: "
  override def foo(msg: String) = println(str2 + msg)
}

class Mixin private(msg: String) extends jijiang {
  def this() = this("mixin")
}
相關文章
相關標籤/搜索