scala——關鍵字trait的使用

1。 特質入門

1。1 概述

有些時候, 我們會遇到一些特定的需求, 即: 在不影響當前繼承體系的情況下, 對某些類(或者某些物件)的功能進行加強, 例如: 有猴子類和大象類, 它們都有姓名, 年齡, 以及吃的功能, 但是部分的猴子經過馬戲團的訓練後, 學會了騎獨輪車。 那騎獨輪車這個功能就不能定義到父類(動物類)或者猴子類中, 而是應該定義到特質中。 而Scala中的特質, 要用關鍵字trait修飾。

1。2 特點

特質可以提高程式碼的複用性。

特質可以提高程式碼的擴充套件性和可維護性。

類與特質之間是繼承關係, 只不過類與類之間只支援單繼承, 但是類與特質之間, 既可以單繼承, 也可以多繼承。

Scala的特質中可以有普通欄位, 抽象欄位, 普通方法, 抽象方法。

注意:如果特質中只有抽象內容, 這樣的特質叫: 瘦介面。如果特質中既有抽象內容, 又有具體內容, 這樣的特質叫: 富介面。

1。3 語法

定義特質

trait 特質名稱 { // 普通欄位 // 抽象欄位 // 普通方法 // 抽象方法}

繼承特質

class 類 extends 特質1 with 特質2 { // 重寫抽象欄位 // 重寫抽象方法}

注意

scala中不管是類還是特質, 繼承關係用的都是extends關鍵字

如果要繼承多個特質(trait),則特質名之間使用with關鍵字隔開

1。4 示例: 類繼承單個特質

需求

建立一個Logger特質,新增log(msg:String)方法

建立一個ConsoleLogger類,繼承Logger特質,實現log方法,列印訊息

新增main方法,建立ConsoleLogger物件,呼叫log方法。

參考程式碼

//案例: Trait入門之類繼承單個特質object ClassDemo01 { //1。 定義一個特質。 trait Logger { def log(msg:String) //抽象方法 } //2。 定義一個類, 繼承特質。 class ConsoleLogger extends Logger { override def log(msg: String): Unit = println(msg) } def main(args: Array[String]): Unit = { //3。 呼叫類中的方法 val cl = new ConsoleLogger cl。log(“trait入門: 類繼承單個特質”) }}

1。5 示例: 類繼承多個trait

需求

建立一個MessageSender特質,新增send(msg:String)方法

建立一個MessageReceiver特質,新增receive()方法

建立一個MessageWorker類, 繼承這兩個特質, 重寫上述的兩個方法

在main中測試,分別呼叫send方法、receive方法

參考程式碼

//案例: 類繼承多個traitobject ClassDemo02 { //1。 定義一個特質: MessageSender, 表示傳送資訊。 trait MessageSender { def send(msg:String) } //2。 定義一個特質: MessageReceiver, 表示接收資訊。 trait MessageReceiver { def receive() } //3。 定義一個類MessageWorker, 繼承兩個特質。 class MessageWorker extends MessageSender with MessageReceiver { override def send(msg: String): Unit = println(“傳送訊息: ” + msg) override def receive(): Unit = println(“訊息已收到, 我很好, 謝謝!。。。”) } //main方法, 作為程式的主入口 def main(args: Array[String]): Unit = { //4。 呼叫類中的方法 val mw = new MessageWorker mw。send(“Hello, 你好啊!”) mw。receive() }}

1。6 示例: object繼承trait

需求

建立一個Logger特質,新增log(msg:String)方法

建立一個Warning特質, 新增warn(msg:String)方法

建立一個單例物件ConsoleLogger,繼承Logger和Warning特質, 重寫特質中的抽象方法

編寫main方法,呼叫單例物件ConsoleLogger的log和warn方法

參考程式碼

//案例: 演示object單例物件繼承特質object ClassDemo03 { //1。 定義一個特質Logger, 新增log(msg:String)方法。 trait Logger{ def log(msg:String) } //2。 定義一個特質Warning, 新增warn(msg:String)方法。 trait Warning{ def warn(msg:String) } //3。 定義單例物件ConsoleLogger, 繼承上述兩個特質, 並重寫兩個方法。 object ConsoleLogger extends Logger with Warning{ override def log(msg: String): Unit = println(“控制檯日誌資訊: ” + msg) override def warn(msg: String): Unit = println(“控制檯警告資訊: ” + msg) } //main方法, 作為程式的入口 def main(args: Array[String]): Unit = { //4。 呼叫ConsoleLogger單例物件中的兩個方法。 ConsoleLogger。log(“我是一條普通日誌資訊!”) ConsoleLogger。warn(“我是一條警告日誌資訊!”) }}

1。7 示例: 演示trait中的成員

需求

定義一個特質Hero, 新增具體欄位name(姓名), 抽象欄位arms(武器), 具體方法eat(), 抽象方法toWar()

定義一個類Generals, 繼承Hero特質, 重寫其中所有的抽象成員。

在main方法中, 建立Generals類的物件, 呼叫其中的成員。

參考程式碼

//案例: 演示特質中的成員object ClassDemo04 { //1。 定義一個特質Hero trait Hero{ var name = “” //具體欄位 var arms:String //抽象欄位 //具體方法 def eat() = println(“吃肉喝酒, 養精蓄銳!”) //抽象方法 def toWar():Unit } //2。 定義一個類Generals, 繼承Hero特質, 重寫其中所有的抽象成員。 class Generals extends Hero { //重寫父特質中的抽象欄位 override var arms: String = “” //重寫父特質中的抽象方法 override def toWar(): Unit = println(s“${name}帶著${arms}, 上陣殺敵!”) } //main方法, 作為程式的入口 def main(args: Array[String]): Unit = { //3。 建立Generals類的物件。 val gy = new Generals //4。 測試Generals類中的內容。 //給成員變數賦值 gy。name = “關羽” gy。arms = “青龍偃月刀” //列印成員變數值 println(gy。name, gy。arms) //呼叫成員方法 gy。eat() gy。toWar() }}

2。 物件混入trait

有些時候, 我們希望在不改變類繼承體系的情況下, 對物件的功能進行臨時增強或者擴充套件, 這個時候就可以考慮使用物件混入技術了。 所謂的物件混入指的就是: 在scala中, 類和特質之間無任何的繼承關係, 但是透過特定的關鍵字, 卻可以讓該類物件具有指定特質中的成員。

2。1 語法

val/var 物件名 = new 類 with 特質

2。2 示例

需求

建立Logger特質, 新增log(msg:String)方法

建立一個User類, 該類和Logger特質之間無任何關係。

在main方法中測試, 透過物件混入技術讓User類的物件具有Logger特質的log()方法。

參考程式碼

//案例: 演示動態混入object ClassDemo05 { //1。 建立Logger特質, 新增log(msg:String)方法 trait Logger { def log(msg:String) = println(msg) } //2。 建立一個User類, 該類和Logger特質之間無任務關係。 class User //main方法, 作為程式的入口 def main(args: Array[String]): Unit = { //3。 在main方法中測試, 透過物件混入技術讓User類的物件具有Logger特質的log()方法。 val c1 = new User with Logger //物件混入 c1。log(“我是User類的物件, 我可以呼叫Logger特質中的log方法了”) }}

3。 使用trait實現介面卡模式

3。1 設計模式簡介

概述

設計模式(Design Pattern)是前輩們對程式碼開發經驗的總結,是解決特定問題的一系列套路。它並不是語法規定,而是一套用來提高程式碼可複用性、可維護性、可讀性、穩健性以及安全性的解決方案。

分類

設計模式一共有23種, 分為如下的3類:

建立型指的是: 需要建立物件的。 常用的模式有: 單例模式, 工廠方法模式

結構型指的是: 類,特質之間的關係架構。 常用的模式有: 介面卡模式, 裝飾模式

行為型指的是: 類(或者特質)能夠做什麼。 常用的模式有:模板方法模式, 職責鏈模式

3。2 介面卡模式

當特質中有多個抽象方法, 而我們只需要用到其中的某一個或者某幾個方法時, 不得不將該特質中的所有抽象方法給重寫了, 這樣做很麻煩。 針對這種情況, 我們可以定義一個抽象類去繼承該特質, 重寫特質中所有的抽象方法, 方法體為空。 這時候, 我們需要使用哪個方法, 只需要定義類繼承抽象類, 重寫指定方法即可。 這個抽象類就叫: 介面卡類。 這種設計模式(設計思想)就叫: 介面卡設計模式。

結構

trait 特質A{ //抽象方法1 //抽象方法2 //抽象方法3 //。。。}abstract class 類B extends A{ //介面卡類 //重寫抽象方法1, 方法體為空 //重寫抽象方法2, 方法體為空 //重寫抽象方法3, 方法體為空 //。。。}class 自定義類C extends 類B { //需要使用哪個方法, 重寫哪個方法即可。}

需求

定義特質PlayLOL, 新增6個抽象方法, 分別為: top(), mid(), adc(), support(), jungle(), schoolchild()

解釋: top: 上單, mid: 中單, adc: 下路, support: 輔助, jungle: 打野, schoolchild: 小學生

定義抽象類Player, 繼承PlayLOL特質, 重寫特質中所有的抽象方法, 方法體都為空。

定義普通類GreenHand, 繼承Player, 重寫support()和schoolchild()方法。

定義main方法, 在其中建立GreenHand類的物件, 並呼叫其方法進行測試。

參考程式碼

//案例: 演示介面卡設計模式。object ClassDemo06 { //1。 定義特質PlayLOL, 新增6個抽象方法, 分別為: top(), mid(), adc(), support(), jungle(), schoolchild() trait PlayLOL { def top() //上單 def mid() //中單 def adc() //下路 def support() //輔助 def jungle() //打野 def schoolchild() //小學生 } //2。 定義抽象類Player, 繼承PlayLOL特質, 重寫特質中所有的抽象方法, 方法體都為空。 //Player類充當的角色就是: 介面卡類。 class Player extends PlayLOL { override def top(): Unit = {} override def mid(): Unit = {} override def adc(): Unit = {} override def support(): Unit = {} override def jungle(): Unit = {} override def schoolchild(): Unit = {} } //3。 定義普通類GreenHand, 繼承Player, 重寫support()和schoolchild()方法。 class GreenHand extends Player{ override def support(): Unit = println(“我是輔助, B鍵一扣, 不死不回城!”) override def schoolchild(): Unit = println(“我是小學生, 你罵我, 我就掛機!”) } //4。 定義main方法, 在其中建立GreenHand類的物件, 並呼叫其方法進行測試。 def main(args: Array[String]): Unit = { //建立GreenHand類的物件 val gh = new GreenHand //呼叫GreenHand類中的方法 gh。support() gh。schoolchild() }}

4。 使用trait實現模板方法模式

在現實生活中, 我們會遇到論文模板, 簡歷模板, 包括PPT中的一些模板等, 而在面向物件程式設計過程中,程式設計師常常會遇到這種情況:設計一個系統時知道了演算法所需的關鍵步驟,而且確定了這些步驟的執行順序,但某些步驟的具體實現還未知,或者說某些步驟的實現與具體的環境相關。

例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對銀行工作人員進行評分等,其中取號、排隊和對銀行工作人員進行評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實現。這就要用到模板方法設計模式了。

4。1 概述

在Scala中, 我們可以先定義一個操作中的演算法骨架,而將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟, 這就是: 模板方法設計模式。

優點

擴充套件性更強。 父類中封裝了公共的部分, 而可變的部分交給子類來實現。

符合開閉原則。部分方法是由子類實現的,因此子類可以透過擴充套件方式增加相應的功能。

缺點

類的個數增加, 導致系統更加龐大, 設計也更加抽象。因為要對每個不同的實現都需要定義一個子類

提高了程式碼閱讀的難度。父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構。

4。2 格式

class A { //父類, 封裝的是公共部分 def 方法名(引數列表) = { //具體方法, 在這裡也叫: 模板方法 //步驟1, 已知。 //步驟2, 未知, 呼叫抽象方法 //步驟3, 已知。 //步驟n。。。 } //抽象方法}class B extends A { //重寫抽象方法}

注意: 抽象方法的個數要根據具體的需求來定, 並不一定只有一個, 也可以是多個。

4。3 示例

需求

定義一個模板類Template, 新增code()和getRuntime()方法, 用來獲取某些程式碼的執行時間。

定義類ForDemo繼承Template, 然後重寫code()方法, 用來計算列印10000次“Hello,Scala!”的執行時間。

定義main方法, 用來測試程式碼的具體執行時間。

參考程式碼

//案例: 演示模板方法設計模式object ClassDemo07 { //1。 定義一個模板類Template, 新增code()和getRuntime()方法, 用來獲取某些程式碼的執行時間。 abstract class Template { //定義code()方法, 用來記錄所有要執行的程式碼 def code() //定義模板方法, 用來獲取某些程式碼的執行時間。 def getRuntime() = { //獲取當前時間毫秒值 val start = System。currentTimeMillis() //具體要執行的程式碼 code() //獲取當前時間毫秒值 val end = System。currentTimeMillis() //返回指定程式碼的執行時間。 end - start } } //2。 定義類ForDemo繼承Template, 然後重寫getRuntime()方法, 用來計算列印10000次“Hello,Scala!”的執行時間。 class ForDemo extends Template { override def code(): Unit = for(i <- 1 to 10000) println(“Hello, Scala!”) } def main(args: Array[String]): Unit = { //3。 測試列印10000次“Hello, Scala!”的執行時間 println(new ForDemo()。getRuntime()) }}

5 使用trait實現職責鏈模式

5。1 概述

多個trait中出現了同一個方法, 且該方法最後都呼叫了super。該方法名(), 當類繼承了這多個trait後, 就可以依次呼叫多個trait中的此同一個方法了, 這就形成了一個呼叫鏈。

執行順序為:

按照從右往左的順序依次執行。即首先會先從最右邊的trait方法開始執行,然後依次往左執行對應trait中的方法

當所有子特質的該方法執行完畢後, 最後會執行父特質中的此方法。

這種設計思想就叫: 職責鏈設計模式。

注意: 在Scala中, 一個類繼承多個特質的情況叫疊加特質。

5。2 格式

trait A { //父特質 def show() //假設方法名叫: show}trait B extends A { //子特質, 根據需求可以定義多個。 override def show() = { //具體的程式碼邏輯。 super。show() }}trait C extends A { override def show() = { //具體的程式碼邏輯。 super。show() }}class D extends B with C { //具體的類, 用來演示: 疊加特質。 def 方法名() = { //這裡可以是該類自己的方法, 不一定非的是show()方法。 //具體的程式碼邏輯。 super。show() //這裡就構成了: 呼叫鏈。 }}/* 執行順序為: 1。 先執行類D中的自己的方法。 2。 再執行特質C中的show()方法。 3。 再執行特質B中的show()方法。 4。 最後執行特質A中的show()方法。*/

5。3 示例

需求

透過Scala程式碼, 實現一個模擬支付過程的呼叫鏈。

解釋:

我們如果要開發一個支付功能,往往需要執行一系列的驗證才能完成支付。例如:

進行支付簽名校驗

資料合法性校驗

。。。

如果將來因為第三方介面支付的調整,需要增加更多的校驗規則,此時如何不修改之前的校驗程式碼,來實現擴充套件呢?

這就需要用到: 職責鏈設計模式了。

步驟

定義一個Handler特質, 新增具體的handle(data:String)方法,表示處理資料(具體的支付邏輯)

定義一個DataValidHandler特質,繼承Handler特質。重寫handle()方法,列印“驗證資料”, 然後呼叫父特質的handle()方法

定義一個SignatureValidHandler特質,繼承Handler特質。重寫handle()方法, 列印“檢查簽名”, 然後呼叫父特質的handle()方法

建立一個Payment類, 繼承DataValidHandler特質和SignatureValidHandler特質定義pay(data:String)方法, 列印“使用者發起支付請求”, 然後呼叫父特質的handle()方法

新增main方法, 建立Payment物件例項, 然後呼叫pay()方法。

參考程式碼

//案例: 演示職責鏈模式(也叫: 呼叫鏈模式)object ClassDemo08 { //1。 定義一個父特質 Handler, 表示處理資料(具體的支付邏輯) trait Handler { def handle(data:String) = { println(“具體處理資料的程式碼(例如: 轉賬邏輯)”) println(data) } } //2。 定義一個子特質 DataValidHandler, 表示 校驗資料。 trait DataValidHandler extends Handler { override def handle(data:String) = { println(“校驗資料。。。”) super。handle(data) } } //3。 定義一個子特質 SignatureValidHandler, 表示 校驗簽名。 trait SignatureValidHandler extends Handler { override def handle(data:String) = { println(“校驗簽名。。。”) super。handle(data) } } //4。 定義一個類Payment, 表示: 使用者發起的支付請求。 class Payment extends DataValidHandler with SignatureValidHandler { def pay(data:String) = { println(“使用者發起支付請求。。。”) super。handle(data) } } def main(args: Array[String]): Unit = { //5。 建立Payment類的物件, 模擬: 呼叫鏈。 val pm = new Payment pm。pay(“蘇明玉給蘇大強轉賬10000元”) }}// 程式執行輸出如下:// 使用者發起支付請求。。。// 校驗簽名。。。// 校驗資料。。。// 具體處理資料的程式碼(例如: 轉賬邏輯)// 蘇明玉給蘇大強轉賬10000元

6。 trait的構造機制

6。1 概述

如果遇到一個類繼承了某個父類且繼承了多個父特質的情況,那該類(子類), 該類的父類, 以及該類的父特質之間是如何構造的呢?

要想解決這個問題, 就要用到接下來要學習的trait的構造機制了。

6。2 構造機制規則

每個特質只有

一個無引數

的構造器。也就是說: trait也有構造程式碼,但和類不一樣,特質不能有構造器引數。

遇到一個類繼承另一個類、以及多個trait的情況,當建立該類的例項時,它的構造器執行順序如下:執行父類的構造器按照從左到右的順序, 依次執行trait的構造器如果trait有父trait,則先執行父trait的構造器。如果多個trait有同樣的父trait,則父trait的構造器只初始化一次。執行子類構造器

6。3 示例

需求

定義一個父類及多個特質,然後用一個類去繼承它們。

建立子類物件, 並測試trait的構造順序

步驟

建立Logger特質,在構造器中列印“執行Logger構造器!”

建立MyLogger特質,繼承自Logger特質,,在構造器中列印“執行MyLogger構造器!”

建立TimeLogger特質,繼承自Logger特質,在構造器中列印“執行TimeLogger構造器!”

建立Person類,在構造器中列印“執行Person構造器!”

建立Student類,繼承Person類及MyLogger, TimeLogge特質,在構造器中列印“執行Student構造器!”

新增main方法,建立Student類的物件,觀察輸出。

參考程式碼

//案例: 演示trait的構造機制。object ClassDemo09 { //1。 建立Logger父特質 trait Logger { println(“執行Logger構造器”) } //2。 建立MyLogger子特質, 繼承Logger特質 trait MyLogger extends Logger { println(“執行MyLogger構造器”) } //3。 建立TimeLogger子特質, 繼承Logger特質。 trait TimeLogger extends Logger { println(“執行TimeLogger構造器”) } //4。 建立父類Person class Person{ println(“執行Person構造器”) } //5。 建立子類Student, 繼承Person類及TimeLogger和MyLogger特質。 class Student extends Person with TimeLogger with MyLogger { println(“執行Student構造器”) } //main方法, 程式的入口。 def main(args: Array[String]): Unit = { //6。 建立Student類的物件,觀察輸出。 new Student }}// 程式執行輸出如下:// 執行Person構造器// 執行Logger構造器// 執行TimeLogger構造器// 執行MyLogger構造器// 執行Student構造器

7。 trait繼承class

7。1 概述

在Scala中, trait(特質)也可以繼承class(類)。特質會將class中的成員都繼承下來。

7。2 格式

class 類A { //類A //成員變數 //成員方法}trait B extends A { //特質B }

7。3 示例

需求

定義Message類。 新增printMsg()方法, 列印“學好Scala, 走到哪裡都不怕!”

建立Logger特質,繼承Message類。

定義ConsoleLogger類, 繼承Logger特質。

在main方法中, 建立ConsoleLogger類的物件, 並呼叫printMsg()方法。

參考程式碼

//案例: 演示特質繼承類object ClassDemo10 { //1。 定義Message類。 新增printMsg()方法, 列印“測試資料。。。” class Message { def printMsg() = println(“學好Scala, 走到哪裡都不怕!”) } //2。 建立Logger特質,繼承Message類。 trait Logger extends Message //3。 定義ConsoleLogger類, 繼承Logger特質。 class ConsoleLogger extends Logger def main(args: Array[String]): Unit = { //4。 建立ConsoleLogger類的物件, 並呼叫printMsg()方法。 val cl = new ConsoleLogger cl。printMsg() }