Scala

好程式設計師IT發表於2019-10-21

  大資料Scala系列之特質, 特質的定義除了使用關鍵字trait之外,與類定義無異。

  特質用來在類之間進行介面或者屬性的共享。類和物件都可以繼承特質,特質不能被例項化,因此也沒有引數。

   一旦特質被定義了,就可以使用extends或者with在類中混入特質。

作為介面使用的特質

特質的定義:

trait Logger{
     // 這是一個抽象方法,特質中未被實現的方法預設是抽象的,不需要 abstract 關鍵字修飾
      def log(msg:String)
}

子類對特質的實現:

class ConsoleLogger  extends Logger{
     // 重寫抽象方法,不需要 override
     def log(msg:String){println(msg)}
}

帶有具體實現的特質

trait ConsoleLogger{
     // 注意與 Java 中介面的不同
       def log(msg:String){println(msg)}
}

特質的使用

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

帶有特質的物件

scala自帶有Logged特質,但是沒有實現

trait Logged{
     def log(msg:String){}
}

如果在類定義中使用了該特質

// 該類中,其中的日誌資訊不會被記錄
class SavingAccount  extends Account  with Logged{
     def withdraw(amount:Double){
         if(amount >balance) log("Insufficent funds")
         else balance -= amount
    }
}

標準的ConsoleLogger擴充套件自Logger

class ConsoleLogger  extends Logger{
     // 重寫抽象方法,不需要 override
     def log(msg:String){println(msg)}
}

可以在建立物件的時候,加入該特質:

val acct1= new SavingAccount  with ConsoleLogger

這樣,建立同一類物件,卻可以加入不同的特質

val acct2= new SavingAccount  with FileLogger

多個疊加的特質

可以為類或者物件新增多個互相呼叫的特質,特質的執行順序,取決於特質被新增的順序

trait Logged{
   def log(msg:String)
}
trait ConsoleLogger  extends Logged{
   // 重寫抽象方法,不需要 override
   def log(msg: String) ={println(msg)}
}
// log 加上時間戳
trait TimestampLogger  extends ConsoleLogger {
   override  def log(msg: String) {
     super.log(s"${java.time.Instant.now()} $msg")
  }
}
// 截斷過於冗長的日誌資訊
trait ShortLogger  extends ConsoleLogger{
     val maxLength = 15
     override  def log(msg: String) {
       super.log(
         if(msg.length <=maxLength)msg
         else
          s"${msg.substring(0,maxLength-3)}...")
    }
  }
// 定義超類
class Account {
   protected  var balance:Double = 0
}
class SavingAccount  extends Account  with ConsoleLogger{
   def withdraw(amount:Double){
     if(amount >balance) log("Insufficent funds")
     else balance = balance - amount
  }
}

object test{
   def main(args: Array[String]): Unit = {
     val acct1 =  new SavingAccount  with ConsoleLogger  with TimestampLogger  with ShortLogger
     val acct2 =  new SavingAccount  with ConsoleLogger  with ShortLogger  with TimestampLogger
    acct1.withdraw(100.0)
    acct2.withdraw(100.0)

  }
}

//res:
//ShortLogger log 方法先被執行,然後它的 super.log 呼叫的是 TimestampLogger  log 方法,最後呼叫 ConsoleLogger  的方法將資訊列印出來
2018-06-15T16:50:28.448Z Insufficent ...
// 先是 TimestampLogger  log 方法被執行,然後它的 super.log 呼叫的是 ShortLogger log 方法,最後呼叫 ConsoleLogger  的方法將資訊列印出來
2018-06-15T1...

使用特質統一程式設計

import scala.collection.mutable.ArrayBuffer

trait Pet {
   val name: String
}

class Cat( val name: String)  extends Pet
class Dog( val name: String)  extends Pet

val dog =  new Dog("Harry")
val cat =  new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))   // Prints Harry Sally

Mixins用於進行類組合的特質:

  abstract  class A {
      val message: String
}
class B  extends A {
   val message = "I'm an instance of class B"
}
// 此處的特質 C 即為 mixin
trait C  extends A {
   def loudMessage = message.toUpperCase()
}
class D  extends B  with C

val d =  new D
println(d.message)   // I'm an instance of class B
println(d.loudMessage)   // I'M AN INSTANCE OF CLASS B

當做富介面使用的特質

// 注意抽象方法和具體方法的結合
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)}
}
class Account {
   protected  var balance:Double = 0
}
class SavingsAccount  extends Account  with Logger {
   def withdraw(amount: Double) {
     if (amount > balance) severe("Insufficient funds")  else "you can do this" }
   override  def log(msg: String) { println(msg) }
}

object test{
   def main(args: Array[String]): Unit = {
     val acc =  new SavingsAccount
    acc.withdraw(100)
  }
}
//result
SEVERE: Insufficient funds

7 特質中的具體欄位和抽象欄位

特質中的欄位有初始值則就是具體的,否則是抽象的。

trait ShortLogger  extends Logged {
   val maxLength = 15    //  具體欄位
}

那麼繼承該特質的子類是如何獲得這個欄位的呢。Scala是直接將該欄位放入到繼承該特製的子類中,而不是被繼承。例如:

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

特質中的抽象欄位在具體的子類中必須被重寫:

trait ShortLogger  extends Logged {
   val maxLength: Int // 抽象欄位
   override  def log(msg: String) {
     super.log(  if (msg.length <= maxLength) msg  else msg.substring(0, maxLength - 3)  + "...")
  }
}

class SavingsAccount  extends Account  with ConsoleLogger  with ShortLogger {
   val maxLength = 20    //  不需要寫 override
}

特質構造順序

特質也是有構造器的,由欄位的初始化和其他特質體中的語句構成:

trait FileLogger  extends Logger {
   val out =  new PrintWriter("app.log")      //  構造器的一部分
  out.println("# " +  new Date().toString)   //  也是構造器的一部分

   def log(msg: String) { out.println(msg); out.flush() }
}

這些語句在任何混入了該特質的物件在構造時都會被執行。   構造器的順序:

  • 首先呼叫超類的構造器
  • 特質構造器在超類構造器之後、類構造器之前執行
  • 特質由左到右被構造
  • 每個特質中,父特質先被構造
  • 如果多個特質共有一個父特質,那麼那個父特質已經被構造,則不會被再次構造
  • 所有特質構造完畢後,子類被構造。  例如:

class SavingsAccount extends Account with FileLogger with ShortLogger

構造器執行順序:

1Account (超類)

2 Logger (第一個特質的父特質)

3 FileLogger

4 ShortLogger

5 SavingsAccount

初始化特質中的欄位

特質不能有構造器引數,每個特質都有一個無參構造器。 這也是特質和類的差別。   例如:  我們要在構造的時候指定log的輸出檔案:

trait FileLogger  extends Logger {
   val filename: String                             //  構造器一部分
   val out =  new PrintWriter(filename)      //  構造器的一部分
   def log(msg: String) { out.println(msg); out.flush() }
}

val acct =  new SavingsAccount  extends Account  with FileLogger("myapp.log")   //error ,特質沒有帶引數的構造器

//  你也許會想到和前面重寫 maxLength 一樣,在這裡重寫 filename:
val acct =  new SavingsAccount  with FileLogger {
   val filename = "myapp.log"    //  這樣是行不通的
}

FileLogger的構造器先於子類構造器執行。這裡的子類其實是一個擴充套件自SavingsAccount 並混入了FileLogger特質的匿名類。而filename的初始化發生在這個匿名類中,而FileLogger的構造器會先執行,因此new PrintWriter(filename)語句會丟擲一個異常。  解決方法是要麼使用提前定義或者使用懶值:

val acct =  new {
   val filename = "myapp.log"
with SavingsAccount  with FileLogger

//  對於類同樣:
class SavingsAccount  extends {
   val filename = "myapp.log"
with Account  with FileLogger { 
  ...    // SavingsAccount  的實現
}

//  或使用 lazy
trait FileLogger  extends Logger {
   val filename: String                             //  構造器一部分
   lazy  val out =  new PrintWriter(filename)      //  構造器的一部分
   def log(msg: String) { out.println(msg); out.flush() }
}

10  擴充套件類的特質

特質也可以擴充套件類,這個類將會自動成為所有混入該特質的超類

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

log方法呼叫了從Exception超類繼承下來的getMessage 方法。那麼混入該特質的類:

class UnhappyException extends LoggedException {
  override def getMessage() = "arggh!"
}

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913892/viewspace-2660823/,如需轉載,請註明出處,否則將追究法律責任。