【Scala】Scala之Traits

leesf發表於2017-04-14

一、前言

  前面學習了Scala中包和匯入的相關知識點,接著學習Traits(特質)

二、Traits

  Scala的特質與Java的介面基本相同,當遇到可以使用Java介面的情形,就可以考慮使用特質,Scala的類可以使用extends和with關鍵字繼承多個特質,如類或物件繼承多個特質  

class Woodpecker extends Bird with TreeScaling with Pecking

  特質除了可以擁有Java中介面的抽象方法,同時還可以擁有已經實現的方法,可以將多餘一個的特質混入類,並且特質可以控制哪些類可以混入該特質

  2.1 將特質作為介面

  1. 問題描述

  像其他語言如Java建立介面一樣,你想在Scala也建立類似東西

  2. 解決方案

  可以將特質類比為Java的介面,在特質中宣告需要子類實現的方法 

trait BaseSoundPlayer {
    def play
    def close
    def pause
    def stop
    def resume
}

  如果方法不帶引數,則只需要寫方法名即可,但若帶引數,需要如下定義  

trait Dog {
    def speak(whatToSay: String)
    def wagTail(enabled: Boolean)
}

  當一個類繼承特質時,需要使用extends和with關鍵字,當繼承一個特質時,使用extends關鍵字  

class Mp3SoundPlayer extends BaseSoundPlayer { ...}

  繼承多個特質時,使用extends和with關鍵字

class Foo extends BaseClass with Trait1 with Trait2 { ...}

  除非實現特質的類是抽象的,否則其需要實現特質中所有方法 

class Mp3SoundPlayer extends BaseSoundPlayer {
    def play { // code here ... }
    def close { // code here ... }
    def pause { // code here ... }
    def stop { // code here ... }
    def resume { // code here ... }
}

   如果沒有實現所有抽象方法,則該類需要被申明為抽象的

abstract class SimpleSoundPlayer extends BaseSoundPlayer {
    def play { ... }
    def close { ... }
}

  特質也可以整合其他特質  

trait Mp3BaseSoundFilePlayer extends BaseSoundFilePlayer {
    def getBasicPlayer: BasicPlayer
    def getBasicController: BasicController
    def setGain(volume: Double)
}

  3. 討論

  特質可被作為Java的介面使用,並且在特質中申明需要被子類實現的方法,當繼承特質時使用extends或者with關鍵字,當繼承一個特質時,使用extends,若繼承多個特質,則第一個使用extends,其他的使用with;若類繼承類(抽象類)和特質,則抽象類使用extends,特質使用with,特質中可以有已經實現了的方法,如WaggingTail 

abstract class Animal {
    def speak
}

trait WaggingTail {
    def startTail { println("tail started") }
    def stopTail { println("tail stopped") }
}

trait FourLeggedAnimal {
    def walk
    def run
}

class Dog extends Animal with WaggingTail with FourLeggedAnimal {
    // implementation code here ...
    def speak { println("Dog says 'woof'") }
    def walk { println("Dog is walking") }
    def run { println("Dog is running") }
}

  2.2 在特質中使用抽象和具體的欄位

  1. 問題描述

  你想要在特質中定義抽象和具體的欄位,以便繼承特質的類可以使用欄位

  2. 解決方案

  使用初始值賦值的欄位是具體的,沒有賦值的欄位是抽象的

trait PizzaTrait {
    var numToppings: Int // abstract
    var size = 14 // concrete
    val maxNumToppings = 10 // concrete
}

  繼承特質的類,需要初始化抽象欄位,或者讓該類為抽象類 

class Pizza extends PizzaTrait {
    var numToppings = 0 // 'override' not needed
    size = 16 // 'var' and 'override' not needed
}

  3. 討論

  特質中的欄位可以被宣告為val或var,你不需要使用override欄位來覆蓋var欄位,但是需要使用override來覆蓋val欄位 

trait PizzaTrait {
    val maxNumToppings: Int
}

class Pizza extends PizzaTrait {
    override val maxNumToppings = 10 // 'override' is required
}

  2.3 像抽象類一樣使用特質

  1. 問題描述

  你想要像Java中的抽象類一樣使用特質

  2. 解決方案

  在特質中定義方法,繼承特質的類中,可以覆蓋該方法,或者是直接使用

trait Pet {
    def speak { println("Yo") } // concrete implementation
    def comeToMaster // abstract method
}

class Dog extends Pet {
    // don't need to implement 'speak' if you don't need to
    def comeToMaster { ("I'm coming!") }
}

class Cat extends Pet {
    // override the speak method
    override def speak { ("meow") }
    def comeToMaster { ("That's not gonna happen.") }
}

  如果一個類沒有實現特質的抽象方法,那麼它也需要被宣告為抽象的

abstract class FlyingPet extends Pet {
    def fly { ("I'm flying!") }
}

  3. 討論

  一個類僅僅只能繼承一個抽象類,但是可以繼承多個特質,使用特質更為靈活

  2.4 把特質作為簡單的混合物

  1. 問題描述

  你想要將多個特質混合進一個類中

  2. 解決方案

  為實現簡單的混合,在特質中定義方法,然後使用extends和with繼承特質,如定義Tail特質 

trait Tail {
    def wagTail { println("tail is wagging") }
    def stopTail { println("tail is stopped") }
}

  可以使用該特質和抽象的Pet類來建立Dog類  

abstract class Pet (var name: String) {
    def speak // abstract
    def ownerIsHome { println("excited") }
    def jumpForJoy { println("jumping for joy") }
}

class Dog (name: String) extends Pet (name) with Tail {
    def speak { println("woof") }
    override def ownerIsHome {
        wagTail
        speak
    }
}

  Dog類通知擁有特質Tail和抽象類Pet的行為

  2.5 通過繼承控制哪個類可以使用特質

  1. 問題描述

    你想限制的特性,只能將其新增至一個父類或者另一個特質的類

  2. 解決方案

  使用下面語法宣告名為TraitName的特質,而TraitName只能被混入繼承了SuperThing的類,SuperThing可以是一個特質、抽象類、類。  

trait [TraitName] extends [SuperThing]

  例如,Starship和StarfleetWarpCore都繼承了StarfleetComponent,所以StarfleetWarpCore特質可以被混入Starship中  

class StarfleetComponent
trait StarfleetWarpCore extends StarfleetComponent
class Starship extends StarfleetComponent with StarfleetWarpCore

  然而,Warbird不能繼承StarfleetWarpCore特質,因為其不繼承StarfleetComponent類 

class StarfleetComponent
trait StarfleetWarpCore extends StarfleetComponent
class RomulanStuff

// won't compile
class Warbird extends RomulanStuff with StarfleetWarpCore 

  3. 討論

  一個特質繼承一個類不是一種普遍情況,但是當其發生時,需要保證擁有相同的父類

  2.6 標記特質以使得其僅僅能被某種型別子類使用

  1. 問題描述

   你想要標記您的特性,因此只能用於擴充套件給定基型別的型別

  2. 解決方案

  保證MyTrait的特質僅僅只能被混入BaseType的子類,可以使用this: BaseType =>宣告開始特質 

trait MyTrait {
    this: BaseType =>

  例如,為了是StarfleetWarpCore特質只能用於Starship,可以標記StarfleetWarpCore特質如下

trait StarfleetWarpCore {
    this: Starship =>
    // more code here ...
}

  給定上面的定義,下面程式碼將會執行良好  

class Starship
class Enterprise extends Starship with StarfleetWarpCore

  而如下程式碼則編譯錯誤  

class RomulanShip
// this won't compile
class Warbird extends RomulanShip with StarfleetWarpCore

  3. 討論

  任何混入特質的具體型別需要保證其能夠轉化為特質的自我型別,特質也可要求繼承其的子類必須繼承多種型別

trait WarpCore {
    this: Starship with WarpCoreEjector with FireExtinguisher =>
}

  如下程式碼中的Enterprise將會通過編譯,因為其簽名滿足特質的定義  

class Starship
trait WarpCoreEjector
trait FireExtinguisher
// this works
class Enterprise extends Starship
with WarpCore
with WarpCoreEjector
with FireExtinguisher

  若Enterprise不繼承特質,則無法滿足簽名,會報錯

  2.7 保證特質只能被混入含有某特定方法的類

  1. 問題描述

  你想要將特質混入包含了某種方法簽名的類

  2. 解決方案

  WarpCore特質要求其所混入的類必須包含ejectWrapCore方法 

trait WarpCore {
    this: { def ejectWarpCore(password: String): Boolean } =>
}

  Enterprise類滿足要求,編譯成功

class Starship {
    // code here ...
}

class Enterprise extends Starship with WarpCore {
    def ejectWarpCore(password: String): Boolean = {
        if (password == "password") {
            println("ejecting core")
            true
        } else {
            false
        }
    }
}

  特質可以要求混入的類包含多個方法 

trait WarpCore {
    this: {
        def ejectWarpCore(password: String): Boolean
        def startWarpCore: Unit
    } =>
}

class Starship
    
class Enterprise extends Starship with WarpCore {
    def ejectWarpCore(password: String): Boolean = {
        if (password == "password") { println("core ejected"); true } else false
    }
    
    def startWarpCore { println("core started") }
}

  3. 討論

  該方法也被稱為結構型別,因為你規定了某些類必須具有的結構

  2.8 將特質新增至物件例項

  1. 問題描述

  當物件例項建立時,你想要混入特質

  2. 解決方案

  可以使用如下方法  

class DavidBanner

trait Angry {
    println("You won't like me ...")
}

object Test extends App {
    val hulk = new DavidBanner with Angry
}

  當執行程式碼時,會出現You won't like me ...結果,因為Angry特質會被例項化

  3. 討論

  混入debugging和logging可能是更為常用的用法 

trait Debugger {
    def log(message: String) {
        // do something with message
    }
}

// no debugger
val child = new Child

// debugger added as the object is created
val problemChild = new ProblemChild with Debugger

  2.9 像特質一樣繼承Java介面

  1. 問題描述

  你想要在Scala應用中實現Java介面

  2. 解決方案

  可以使用extends和with關鍵字繼承介面,如同繼承特質一樣,給定如下Java程式碼

// java
public interface Animal {
    public void speak();
}

public interface Wagging {
    public void wag();
}

public interface Running {
    public void run();
}

  你可以以Scala方式建立Dog類

// scala
class Dog extends Animal with Wagging with Running {
    def speak { println("Woof") }
    def wag { println("Tail is wagging!") }
    def run { println("I'm running!") }
}

  區別在於Java介面不能實現方法,所以當繼承介面時,要麼實現所有方法,要麼宣告為抽象的

三、總結

  本篇博文講解了Scala中的特質相關點,其可以類比於Java的介面,但是比介面更為靈活,如可新增欄位和已經實現方法(在Java 8後的介面也可以新增已實現的方法),謝謝各位園友的觀看~ 

相關文章