Chapter09 特質

1. 爲什麼沒有多重繼承

  • 準備這個Chapter講文件和正則表達式,但該內容按照其他語言安排一般都是在最後幾章節,所以暫時先忽略該章節,後面會補上。

  • 轉入正題,如果有兩個類Student和Employee,它們都有name這個屬性,如果要同時擴展這兩個基類該如何做,主要問題是name屬性如何處理?如果保留兩個,那麼訪問時到底訪問的是哪個呢?如果只保留一個,那麼保留哪一個?該問題就是菱形問題。畫一個形象的圖形如下:

    這裏寫圖片描述

  • Scala通過提供特質解決這個問題,特質可以同時擁有抽象方法和具體方法,而類可以實現多個特質。


2. 當做接口使用的特質

  • 特質完全可以像Java接口那樣工作。

  • 特質中未被實現的方法默認是抽象方法不需要abstract申明。

  • 重寫特質抽象方法時不需要給出override關鍵字。

  • 特質不止一個,可以使用with關鍵字來添加額外的特質。

  • Scala類只能有一個超類,可以有任意數量的特質


3. 帶有具體實現的特質

  • 特質中可以有具體的方法。
    trait ConsoleLogger {
      def log(msg: String) { println(msg) } // 具體方法
    }

    class Account {
      protected var balance = 0.0
    }

    class SavingsAccount extends Account with ConsoleLogger {
      def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }

4. 帶有特質的對象

  • 構造單個對象時,可以爲它添加特質,用with關鍵字。
    trait Logged {
      def log(msg: String) { }
    }

    trait ConsoleLogger extends Logged { 
      override def log(msg: String) { println(msg) }
    } 

    class Account {
      protected var balance = 0.0
    }

    class SavingsAccount extends Account with Logged {
      def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }

    object Main extends App {
      val acct1 = new SavingsAccount
      acct1.withdraw(100) // 沒有任何信息會打印出來

      println("Overdrawing acct2");
      val acct2 = new SavingsAccount with ConsoleLogger // 添加特質進來
      acct2.withdraw(100) // 會打印信息
    }

5. 添加多個特質

個人理解:添加多個特質要研究的問題實際是,添加的這幾個特質哪個先執行?即執行的順序,執行完的結果是要傳給另一個特質進行下一步處理,這裏有點像linux中pipe的概念。

  • 類或對象可以添加多個特質,特質從最後一個開始執行,非常適合分階段處理的場景。
    trait Logged {
      def log(msg: String) { }
    }

    trait ConsoleLogger extends Logged {
      override def log(msg: String) { println(msg) }
    }

    trait TimestampLogger extends Logged { // 給消息添加時間戳
      override def log(msg: String) {
        super.log(new java.util.Date() + " " + msg)
      }
    }

    trait ShortLogger extends Logged { // 截斷過長的信息
      val maxLength = 15 
      override def log(msg: String) {
        super.log(
          if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
      }
    }

    class Account {
      protected var balance = 0.0
    }

    class SavingsAccount extends Account with Logged {
      def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds") // 調用特質中的log方法
        else balance -= amount
      }
    }

    object Main extends App {
      val acct1 = new SavingsAccount with ConsoleLogger with
        TimestampLogger with ShortLogger
      val acct2 = new SavingsAccount with ConsoleLogger with
        ShortLogger with TimestampLogger
      acct1.withdraw(100) 
      acct2.withdraw(100)
    }
  • 由於特質添加的順序,上面的執行結果爲:
    運行結果

6. 在特質中重寫抽象方法

個人理解: 這節就是想區分用類來擴展特質是不需要加override,而特質擴展特質就要加override。

  • 特質中重寫抽象方法需要加abstractoverride關鍵字。
    trait Logger {
      def log(msg: String) // 抽象的方法
    }

    trait TimestampLogger extends Logger {
      abstract override def log(msg: String) {
        super.log(new java.util.Date() + " " + msg)
      }
    }

    trait ConsoleLogger extends Logger {
      override def log(msg: String) { println(msg) } // 這裏說明abstract是可以省略的
    }

7. 當做富接口使用的特質

*個人理解:這節無非是上面的基本應用,train中可以根據不同的應用場景來定義多個同一功能的方法。

  • 比如下面這段代碼,每個日誌消息都會區分消息的類型。
    trait Logger {
      def log(msg: String)
      def info(msg: String) { log("INFO: " + msg) }
      def warn(msg: String) { log("WARN: " + msg) }
      def severe(msg: String) { log("SEVERE: " + msg) }
    }

8. 特質中的具體字段

個人理解:這節主要目的是知道特質中可以有具體的字段,具體的意思就是經過初始化的字段。並且在擴展特質後,具體字段不是被繼承的,而是直接被加入到子類中。說白了就是在類中又拷貝了該字段的一個副本,類中可以直接使用。


9. 特質中的抽象字段

個人理解:具體字段可以直接使用,那麼如果是抽象字段該怎麼辦?這節給出特質中如果有抽象字段,中使用就必須要重新定義該字段,並且不需要使用override關鍵字,這在第二小節已經明確。

    trait Logger {
      val maxLength: Int // 抽象的字段
    }

    class TimestampLogger extends Logger {
      val maxLength: Int = 20 // 不需要寫override
    }

10. 特質中的構造順序

個人理解:和類一樣,特質也有構造器,同樣特質中也存在構造執行順序的問題。記住特質的構造順序對寫程序是很有幫助的。

  • 特質的構造器執行順序:

    1. 首先調用超類構造器;
    2. 特質構造器在超類構造器之後、類構造器之前執行;
    3. 特質由左到右構造;
    4. 每個特質中,父特質先被構造;
    5. 如果過個特質共有一個父特質,而父特質已被構造,則不會再次被構造;
    6. 所有特質構造完畢,子類被構造。

11. 初始化特質中的字段

個人理解:首先知道特質是不能有構造參數,對某種定製的特質來說這就是一個問題,那麼該如何解決?這小結給給出了兩種解決方案:提前定義懶值

  • 特質不能有構造參數,但都有一個無參的構造器,缺少構造器參數這是特質和類之間唯一的技術差別,特質可以具備類的所有特性。

  • 提前定義的方法如下:

    trait Logger {
      def log(msg: String)
    }

    // 使用提前定義定義變量filename
    trait FileLogger extends Logger {
      val filename: String
      val out = new PrintWriter(filename) 
      def log(msg: String) { out.println(msg); out.flush() }
    }

    class Account {
      protected var balance = 0.0
    }

    abstract class SavingsAccount extends Account with Logger {
      def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }

    object Main extends App {
      val acct = new {  // new之後就是提前定義塊,在構造器執行前就定義好
        val filename = "myapp.log"
      } with SavingsAccount with FileLogger
      acct.withdraw(100) 

    }
  • 懶值方法,懶值字段會在第一次使用時纔會被初始化。
    trait Logger {
      def log(msg: String)
    }

    // 使用懶值
    trait FileLogger2 extends Logger {
      val filename: String
      lazy val out = new PrintWriter(filename) 
      def log(msg: String) { out.println(msg); out.flush() }
    }

    class Account {
      protected var balance = 0.0
    }

    abstract class SavingsAccount extends Account with Logger {
      def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
        else balance -= amount
      }
    }

    object Main extends App {
      val acct2 = new SavingsAccount with FileLogger2 {
        val filename = "myapp2.log"
      } 
      acct2.withdraw(100)   
    }

12. 擴展類的特質

個人理解:標題有點繞,其實意思就是某個特質擴展了類,這種情況scala會有哪些特殊的地方需要注意。

  • 特質是可以擴展特質的,其實特質也是可以擴展類,擴展後這個類自動成爲該特質的超類。
    trait Logged {
      def log(msg: String) { }
    }

    trait LoggedException extends Exception with Logged {
      def log() { log(getMessage()) }
    }

這裏LoggedException擴展了Exception類,並調用了從Exception超類繼承下來的getMessage()方法。


13. 自身類型

個人理解:除上面可以擴展某個類的方法之外,scala還提供另外一種方式,不用擴展某個類,直接把某個類作爲自身的一個類型,從而達到同樣的目的。

  • 定義語法:this: 類型 =>,特質定義自身類型後,只能混入指定類型的子類。比如:
    trait Logged {
      def log(msg: String) { }
    }

    trait LoggedException extends Logged {
      this: Exception => // 特質不是擴展Exception類,而是有一個自身類型Exception
        def log() { log(getMessage()) }
    }
  • 自身類型還可以用來處理結構類型,比如只指定一個方法,而不是類名。
    trait Logged {
      def log(msg: String) { }
    }

    trait LoggedException extends Logged {
      this: { def getMessage() : String } =>
        def log() { log(getMessage()) }
    }

【待續】

發佈了84 篇原創文章 · 獲贊 324 · 訪問量 66萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章