scala學習筆記-面向物件程式設計之Trait
阿新 • • 發佈:2018-11-05
將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 }