1. 程式人生 > >scala學習筆記-面向物件程式設計之Trait

scala學習筆記-面向物件程式設計之Trait

將trait作為介面使用

複製程式碼
 1 // Scala中的Triat是一種特殊的概念
 2 // 首先我們可以將Trait作為介面來使用,此時的Triat就與Java中的介面非常類似
 3 // 在triat中可以定義抽象方法,就與抽象類中的抽象方法一樣,只要不給出方法的具體實現即可
 4 // 類可以使用extends關鍵字繼承trait,注意,這裡不是implement,而是extends,在scala中沒有implement的概念,無論繼承類還是trait,統一都是extends
 5 // 類繼承trait後,必須實現其中的抽象方法,實現時不需要使用override關鍵字
 6 // scala不支援對類進行多繼承,但是支援多重繼承trait,使用with關鍵字即可
 7 
 8 trait HelloTrait {
 9   def sayHello(name: String)
10 }
11 trait MakeFriendsTrait {
12   def makeFriends(p: Person)
13 }
14 class Person(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable {
15   def sayHello(name: String) = println("Hello, " + name)
16   def makeFriends(p: Person) = println("Hello, my name is " + name + ", your name is " + p.name)
17 }
複製程式碼

在Trait中定義具體方法

複製程式碼
 1 // Scala中的Triat可以不是隻定義抽象方法,還可以定義具體方法,此時trait更像是包含了通用工具方法的東西// 有一個專有的名詞來形容這種情況,就是說trait的功能混入了類
 2 // 舉例來說,trait中可以包含一些很多類都通用的功能方法,比如列印日誌等等,spark中就使用了trait來定義了通用的日誌列印方法
 3 
 4 trait Logger {
 5   def log(message: String) = println(message)
 6 }
 7 
 8 class Person(val name: String) extends Logger {
 9   def makeFriends(p: Person) {
10     println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
11     log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
12   }
13 }
複製程式碼

在Trait中定義具體欄位

複製程式碼
 1 // Scala中的Triat可以定義具體field,此時繼承trait的類就自動獲得了trait中定義的field
 2 // 但是這種獲取field的方式與繼承class是不同的:如果是繼承class獲取的field,實際是定義在父類中的;而繼承trait獲取的field,就直接被新增到了類中
 3 
 4 trait Person {
 5   val eyeNum: Int = 2
 6 }
 7 
 8 class Student(val name: String) extends Person {
 9   def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
10 }
複製程式碼

在Trait中定義抽象欄位

複製程式碼
// Scala中的Triat可以定義抽象field,而trait中的具體方法則可以基於抽象field來編寫
// 但是繼承trait的類,則必須覆蓋抽象field,提供具體的值

trait SayHello {
  val msg: String
  def sayHello(name: String) = println(msg + ", " + name)
}

class Person(val name: String) extends SayHello {
  val msg: String = "hello"
  def makeFriends(p: Person) {
    sayHello(p.name)
    println("I'm " + name + ", I want to make friends with you!")
  }
}
複製程式碼

為例項混入trait

複製程式碼
 1 // 有時我們可以在建立類的物件時,指定該物件混入某個trait,這樣,就只有這個物件混入該trait的方法,而類的其他物件則沒有
 2 
 3 trait Logged {
 4   def log(msg: String) {}
 5 }
 6 trait MyLogger extends Logged {
 7   override def log(msg: String) { println("log: " + msg) }
 8 }  
 9 class Person(val name: String) extends Logged {
10     def sayHello { println("Hi, I'm " + name); log("sayHello is invoked!") }
11 }
12 
13 val p1 = new Person("leo")
14 p1.sayHello
15 val p2 = new Person("jack") with MyLogger
16 p2.sayHello
複製程式碼

trait呼叫鏈

複製程式碼
 1 // Scala中支援讓類繼承多個trait後,依次呼叫多個trait中的同一個方法,只要讓多個trait的同一個方法中,在最後都執行super.方法即可
 2 // 類中呼叫多個trait中都有的這個方法時,首先會從最右邊的trait的方法開始執行,然後依次往左執行,形成一個呼叫鏈條
 3 // 這種特性非常強大,其實就相當於設計模式中的責任鏈模式的一種具體實現依賴
 4 trait Handler {
 5   def handle(data: String) {}
 6 }
 7 trait DataValidHandler extends Handler {
 8   override def handle(data: String) {
 9     println("check data: " + data)
10     super.handle(data)
11   } 
12 }
13 trait SignatureValidHandler extends Handler {
14   override def handle(data: String) {
15     println("check signature: " + data)
16     super.handle(data)
17   }
18 }
19 class Person(val name: String) extends SignatureValidHandler with DataValidHandler {
20   def sayHello = { println("Hello, " + name); handle(name) }
21 }
22 
複製程式碼

在trait中覆蓋抽象方法

複製程式碼
 1 // 在trait中,是可以覆蓋父trait的抽象方法的
 2 // 但是覆蓋時,如果使用了super.方法的程式碼,則無法通過編譯。因為super.方法就會去掉用父trait的抽象方法,此時子trait的該方法還是會被認為是抽象的
 3 // 此時如果要通過編譯,就得給子trait的方法加上abstract override修飾
 4 
 5 trait Logger {
 6   def log(msg: String)
 7 }
 8 
 9 trait MyLogger extends Logger {
10   abstract override def log(msg: String) { super.log(msg) }
11 }
12 
13 
14 
複製程式碼

混合使用trait的具體方法和抽象方法

複製程式碼
 1 // 在trait中,可以混合使用具體方法和抽象方法
 2 // 可以讓具體方法依賴於抽象方法,而抽象方法則放到繼承trait的類中去實現
 3 // 這種trait其實就是設計模式中的模板設計模式的體現
 4 
 5 trait Valid {
 6   def getName: String
 7   def valid: Boolean = {
 8     getName == "leo"    
 9   }
10 }
11 class Person(val name: String) extends Valid {
12   println(valid)
13   def getName = name
14 }
15 
16 
17 
18 
複製程式碼

trait的構造機制

複製程式碼
 1 // 在Scala中,trait也是有構造程式碼的,也就是trait中的,不包含在任何方法中的程式碼
 2 // 而繼承了trait的類的構造機制如下:1、父類的建構函式執行;2、trait的構造程式碼執行,多個trait從左到右依次執行;3、構造trait時會先構造父trait,如果多個trait繼承同一個父trait,則父trait只會構造一次;4、所有trait構造完畢之後,子類的建構函式執行
 3 
 4 class Person { println("Person's constructor!") }
 5 trait Logger { println("Logger's constructor!") }
 6 trait MyLogger extends Logger { println("MyLogger's constructor!") }
 7 trait TimeLogger extends Logger { println("TimeLogger's constructor!") }
 8 class Student extends Person with MyLogger with TimeLogger {
 9   println("Student's constructor!")
10 }
11 
12 
13 
14 
15 
16 
複製程式碼

trait field的初始化

複製程式碼
 1 // 在Scala中,trait是沒有接收引數的建構函式的,這是trait與class的唯一區別,但是如果需求就是要trait能夠對field進行初始化,該怎麼辦呢?只能使用Scala中非常特殊的一種高階特性——提前定義
 2 trait SayHello {
 3   val msg: String
 4   println(msg.toString)
 5 }
 6 
 7 class Person
 8 val p = new {
 9   val msg: String = "init"
10 } with Person with SayHello
11 
12 class Person extends {
13   val msg: String = "init"
14 } with SayHello {}
15 
16 // 另外一種方式就是使用lazy value
17 trait SayHello {
18   lazy val msg: String = null
19   println(msg.toString)
20 }
21 class Person extends SayHello {
22   override lazy val msg: String = "init"
23 }
24 
25 
26 
27 
複製程式碼

trait繼承class

複製程式碼
 1 // 在Scala中,trait也可以繼承自class,此時這個class就會成為所有繼承該trait的類的父類
 2 
 3 class MyUtil {
 4   def printMessage(msg: String) = println(msg)
 5 }
 6 
 7 trait Logger extends MyUtil {
 8   def log(msg: String) = printMessage("log: " + msg)
 9 }
10 
11 class Person(val name: String) extends Logger {
12   def sayHello {
13     log("Hi, I'm " + name)
14     printMessage("Hi, I'm " + name)
15   }
16 }