您好,登錄后才能下訂單哦!
這篇文章主要介紹Scala中Traits功能是什么,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
Traits 介紹
在我們深入面向?qū)ο缶幊讨?,我們還需要了解Scala 一個特性:Traits。要了解這個功能需要一點歷史知識。
在Java 中,一個類可以實現(xiàn)任意數(shù)量的接口。這個模型在聲明一個類實現(xiàn)多個抽象的時候非常有用。不幸的是,它也有一個主要缺點。
對于許多接口,大多數(shù)功能都可以用對于所有使用這個接口的類都有效的“樣板”代碼來實現(xiàn)。Java 沒有提供一個內(nèi)置機(jī)制來定義和使用這些可重用代碼。相反的,Java 程序員必須使用一個特別的轉(zhuǎn)換來重用一個已知接口的實現(xiàn)。在最壞的情況下,程序員必須復(fù)制粘貼同樣的代碼到不同的類中去。
通常,一個接口的實現(xiàn)擁有和該實例的其它成員無關(guān)(正交)的成員。術(shù)語mixin (混合)通常被用來指實例中這些專注的,潛在可被重用的,并且可以獨立維護(hù)的代碼。
來看一下下面這個圖形用戶接口的按鈕的代碼,它對單擊事件使用了回調(diào)。
// code-examples/Traits/ui/button-callbacks.scala package ui class ButtonWithCallbacks(val label: String, val clickedCallbacks: List[() => Unit]) extends Widget { require(clickedCallbacks != null, "Callback list can't be null!") def this(label: String, clickedCallback: () => Unit) = this(label, List(clickedCallback)) def this(label: String) = { this(label, Nil) println("Warning: button has no click callbacks!") } def click() = { // ... logic to give the appearance of clicking a physical button ... clickedCallbacks.foreach(f => f()) } }
這里發(fā)生了很多事情。主構(gòu)造函數(shù)接受一個label(標(biāo)簽)參數(shù)和一個callbacks(回調(diào))的list(列表),這些回調(diào)函數(shù)會在按鈕的click 方法被調(diào)用時被調(diào)用。我們會在《第5章 - Scala 基礎(chǔ)面向?qū)ο缶幊獭分衼硖剿鬟@個類的更多細(xì)節(jié)。現(xiàn)在,我們希望專注在一個特別的問題上。ButtonWithCallbacks 不僅處理按鈕的一些本質(zhì)行為(比如單擊),它同時還通過調(diào)用回調(diào)函數(shù)處理單擊事件的通知。這違反了單職原則[Martin2003],這是分隔職能的一種設(shè)計方法。我們可以把按鈕類的邏輯從回調(diào)邏輯中分離出來,這樣每一個邏輯組件變得更加簡單,更模塊化,更可重用化。這個回調(diào)邏輯就是mixin 的一個不錯例子。
這樣的分離在Java 中很難做,即使我們定義了具有回調(diào)行為的接口,我們?nèi)匀恍枰陬愔屑蓪崿F(xiàn)代碼,降低模塊性。唯一的其它方法則是使用特定的工具比如面向方面編程(Aspect-Oriented Programming,AOP,參見[AOSD]),一個實現(xiàn)是AspectJ,Java 的一個擴(kuò)展。AOP 主要被設(shè)計用來分離在應(yīng)用程序中重復(fù)出現(xiàn)的普遍問題的實現(xiàn)。它設(shè)法模塊化這些關(guān)鍵點,但是也允許設(shè)計良好的和其它關(guān)鍵點行為的“混合”,包括應(yīng)用程序的核心域邏輯,不管是在編譯時還是運(yùn)行時。
作為混合體的Traits
Scala 提供了完整的混合(mixin)解決方案,稱為Traits。在我們的例子里,我們可以定義回調(diào)的抽象為一個Trait,就像一個Java 接口一樣,但是我們可以實現(xiàn)這些Trait (或者繼承的Trait)的抽象。我們可以定義混合了Trait 的類,大致上很像實現(xiàn)Java 的一個接口。不過,在Scala 你甚至可以在我們創(chuàng)建實例的時候混合Traits。也就是說,我們不必首先聲明一個混合了所有我們所需要的Trait 的類。所以,Scala Traits 在保留分離關(guān)鍵點的同時給了我們按需整合行為的能力。
如果你來自于Java 編程世界,你可以認(rèn)為Traits 是有選擇地實現(xiàn)了的接口。其它語言提供了類似Trait 的結(jié)構(gòu),比如Ruby 中的模塊(modules)。
讓我們對按鈕的邏輯來使用Trait,從而分離回調(diào)的處理。我們會推廣一下我們的實現(xiàn)?;卣{(diào)其實是觀察者模式[GOF1995] 的一個特例。所以,讓我們創(chuàng)建一個Trait 來實現(xiàn)這個模式,然后用它來處理回調(diào)行為。為了簡單化,我們從一個單獨的計算按鈕被按次數(shù)的回調(diào)開始。
首先,讓我們定義一個簡單的Button 類。
// code-examples/Traits/ui/button.scala package ui class Button(val label: String) extends Widget { def click() = { // Logic to give the appearance of clicking a button... } }
這里是它的父類,Widget。
// code-examples/Traits/ui/widget.scala package ui abstract class Widget
管理回調(diào)的邏輯(比如,clickedCallbacks 列表)被省略了,兩個主要構(gòu)造函數(shù)也是。只有按鈕的label 字段和click 方法被保留了下來。這個click 方法現(xiàn)在只關(guān)心一個“物理上的” 按鈕被單擊時候的可見表現(xiàn)。按鈕只有一個關(guān)心的東西,就是處理作為一個按鈕的本質(zhì)行為。
這里是一個實現(xiàn)了觀察者模式邏輯的Trait。
// code-examples/Traits/observer/observer.scala package observer Trait Subject { type Observer = { def receiveUpdate(subject: Any) } private var observers = List[Observer]() def addObserver(observer:Observer) = observers ::= observer def notifyObservers = observers foreach (_.receiveUpdate(this)) }
除了Trait 關(guān)鍵字,Subject 看起來就像一個普通的類。Subject 定義了所有它聲明的成員。Traits 可以聲明抽象成員,具體成員,或者兩者皆有,就像類所能做的一樣(參見《第6章 - Scala 高級面向?qū)ο缶幊獭返摹爸貙戭惡蚑raits 的成員”章節(jié)獲取更多信息)。而且,Traits 能包含嵌套的Trait 和類定義,類也能包含嵌套的Trait 定義。
第一行定義了Observer 類型。這是一個結(jié)構(gòu)類型,形式是 { def receiveUpdate(subject:Any) }。結(jié)構(gòu)類型僅制定了子類型必須支持的結(jié)構(gòu);你可以把它們看作是“匿名”類型。
在這個例子里,這個結(jié)構(gòu)類型由一個有著特定簽名的方法定義。任何有這個簽名的方法的類型都可以被用作為一個observer(觀察者)。我們會在《第12章 - Scala 類型系統(tǒng)》學(xué)習(xí)更多有關(guān)結(jié)構(gòu)類型的內(nèi)容。如果你想知道為什么我們不把Subject 作為參數(shù),而是Any。我們會在《第13章 - 應(yīng)用程序設(shè)計》的“自我類型注解和抽象類型成員”章節(jié)來重習(xí)這個問題。
我們所需要注意的最主要的是這樣的結(jié)構(gòu)類型如何最小化了Subject Trait 和任何潛在的Trait 用戶之間的耦合。
注意
Subject 仍然通過結(jié)構(gòu)類型和Observer 中的方法名稱耦合在一起,例如,名為receiveUpdate 的方法。我們有幾種方法來省去這剩下的耦合。我們會在《第6章 - Scala 高級面向?qū)ο缶幊獭分械摹爸貙懗橄箢愋汀闭鹿?jié)看到如何做到這一點。
下面,我們聲明了一系列觀察者。我們定義了一個var,而不是val,因為List 是不可變的。所以我們必須在一個觀察者通過addObserver 方法被添加時創(chuàng)建一個新的列表。
我們會在《第7章 - Scala 對象系統(tǒng)》的“Scala 類型結(jié)構(gòu)”章節(jié)和《第8章 - Scala 函數(shù)式編程》中討論更多有關(guān)List 的細(xì)節(jié)?,F(xiàn)在,注意addObserver 使用了列表的cons “操作符”方法(::)來在一個列表前面加入一個觀察者。Scala 編譯器會聰明地把下面的語句,
observers ::= observer
轉(zhuǎn)換成如下語句,
observerobservers = observer :: observers
注意我們寫了observer:: observers,把已存的observers 列表放到了右邊。回憶一下,所有的以: 結(jié)尾的方法是右綁定的。所以,前一個語句和下面的語句等價。
observersobservers = observers.::(observer)
notifyObservers 方法遍歷所有的觀察者,使用foreach 方法,然后對每一個觀察者調(diào)用receiveUpdate 方法。(注意我們使用了“插入”操作符標(biāo)記法而不是observers.foreach。)我們使用占位符'_' 來縮短下面的表達(dá)式,
(obs) => obs.receiveUpdate(this)
為這樣的表達(dá)式,
_.receiveUpdate(this)
這個表達(dá)式實際上是一個“匿名函數(shù)”的函數(shù)體,在Scala 中稱為字面函數(shù)。這和其它語言中的Lambda 表達(dá)式或類似結(jié)構(gòu)相似。字面函數(shù)和閉包相關(guān)的概念會在《第8章 - Scala 函數(shù)式編程》的“字面函數(shù)和閉包”章節(jié)中被討論。
在Java 中,foreach 方法很可能會接受一個接口,你可能會傳遞一個實現(xiàn)了該接口的類的實例。(比如,典型的Comparable 使用的方法)。
在Scala 中,List[A].foreach 方法期待的參數(shù)類型為(A)=>Unit,這是一個函數(shù),接受一個A 類型的參數(shù),而A 標(biāo)識了列表的元素的類型(在這個例子中是Observer),然后返回Unit(和Java 的void 一樣)。
注意
我們在這個例子里選擇使用一個var 來表示不可變的觀察者的List。我們也可以使用val 和一個可變的類型,比如ListBuffer。這個選擇會在一個真實的應(yīng)用程序中顯得更加合理,但是我們希望避免介紹新的類來分散我們的注意。
再一次的,我們從一個小例子里學(xué)習(xí)了許多Scala 的知識?,F(xiàn)在,讓我們來用一用我們的Subject Trait。這里有一個ObservableButton,它繼承了Button,混合了Subject。
// code-examples/Traits/ui/observable-button.scala package ui import observer._ class ObservableButton(name: String) extends Button(name) with Subject { override def click() = { super.click() notifyObservers } }
我們從導(dǎo)入observer 包的所有東西開始,使用'_' 通配符。實際上,我們在這個包中只定義了Subject Trait。
新的類使用了with 關(guān)鍵字把Subject Trait 加到類中。ObserverButton 重寫了click 方法。使用super 關(guān)鍵字(參見《第6章 - Scala 高級面向?qū)ο缶幊獭返摹爸貙懗橄蠛途唧w方法”章節(jié)),它首先調(diào)用了“父類”的方法,Button.click,然后它通知觀察者。因為新的click 方法重寫了Button 的具體實現(xiàn),必須加上override 關(guān)鍵字。
with 關(guān)鍵字和Java 的對接口使用的implement 關(guān)鍵字類似。你可以是頂任意多的Traits,每一個都必須有with 關(guān)鍵字。
一個類可以繼承一個Trait,一個Trait 也可以繼承一個類。實際上,我們的Widget 類也可以被聲明為一個Trait。
注意
如果你定義一個類使用一個或多個Traits,而它又不繼承任何類,你必須對第一個列出的Trait 使用extends 關(guān)鍵字。
如果你不對第一個Trait 使用extends,例如寫成這樣。
// ERROR: class ObservableButton(name: String) with Button(name) with Subject {...}
你會獲得如下錯誤。
... error: ';' expected but 'with' found. class ObservableButton(name: String) with Button(name) with Subject {...} ^
這個錯誤實際上應(yīng)該說“with found,but extends expected?!保òl(fā)現(xiàn)with 關(guān)鍵字,但是期望一個extends。)
要演示這部分代碼,讓我們從一個觀察按鈕點擊和記錄點擊數(shù)目的類開始。
// code-examples/Traits/ui/button-count-observer.scala package ui import observer._ class ButtonCountObserver { var count = 0 def receiveUpdate(subject: Any) = count += 1 }
左后,讓我們寫一個測試來運(yùn)用所有的類。我們會使用Specs 庫(在《第14章 - Scala 工具,庫和IDE 支持》的“Specs” 章節(jié)討論) 來寫一個行為驅(qū)動(【BDD】)的“規(guī)范”來測試組合后的Button 和Subject 類型。
// code-examples/Traits/ui/button-observer-spec.scala package ui import org.specs._ import observer._ object ButtonObserverSpec extends Specification { "A Button Observer" should { "observe button clicks" in { val observableButton = new ObservableButton("Okay") val buttonObserver = new ButtonCountObserver observableButton.addObserver(buttonObserver) for (i <- 1 to 3) observableButton.click() buttonObserver.count mustEqual 3 } } }
如果你從O'Reilly 網(wǎng)站下載了代碼例子,你可以按照README 文件的指示來編譯和運(yùn)行這個章節(jié)的例子。specs “目標(biāo)”的輸出應(yīng)該包含如下的內(nèi)容。
Specification "ButtonCountObserverSpec" A Button Observer should + observe button clicks Total for specification "ButtonCountObserverSpec": Finished in 0 second, 10 ms 1 example, 1 expectation, 0 failure, 0 error
注意字符串“A Button Observer Should”和“observe button clicks” 對應(yīng)了例子中的字符串。Specs 的輸出提供了一個漂亮的被測試的項目的需求,并假設(shè)為這些字符串做了合適的決定。
測試的主題創(chuàng)建了一個“Okay” ObservableButton 和一個ButtonCountObserver,把觀察者給了這個button。按鈕通過for 循環(huán)被按了3次。最后一行要求observer 的計數(shù)等于3。如果你習(xí)慣使用XUnit 風(fēng)格的TDD (測試驅(qū)動開發(fā))工具,例如JUnit 或者ScalaTest(參見《第14章 - Scala 工具,庫和IDE 支持》的“ScalaTest” 章節(jié)),那么最后一行等效于下面的JUnit 斷言。
assertEquals(3, buttonObserver.count)
注意
Specs 庫(參見“Specs” 章節(jié)) 和ScalaTest 庫(參見“ScalaTest”章節(jié))都支持行為驅(qū)動開發(fā)[BDD],測試驅(qū)動開發(fā)[TDD] 的一種風(fēng)格,強(qiáng)調(diào)了測試的“規(guī)范”角色。
假設(shè)我們只需要一個ObservableButton 實例呢?我們實際上不用聲明一個繼承Subject 的Button 的子類。我們可以在創(chuàng)建實例的時候加上Trait。
下一個例子展示了一個修訂的Specs 文件,它實例化了一個Button,混合了Subject 作為聲明的一部分。
// code-examples/Traits/ui/button-observer-anon-spec.scala package ui import org.specs._ import observer._ object ButtonObserverAnonSpec extends Specification { "A Button Observer" should { "observe button clicks" in { val observableButton = new Button("Okay") with Subject { override def click() = { super.click() notifyObservers } } val buttonObserver = new ButtonCountObserver observableButton.addObserver(buttonObserver) for (i <- 1 to 3) observableButton.click() buttonObserver.count mustEqual 3 } } }
修訂過的observableButton 的聲明實際上創(chuàng)建了一個匿名類,并且像之前那樣重寫了click 方法。和在Java 中創(chuàng)建匿名類的主要區(qū)別是我們可以在過程中引入Trait。Java 不允許你在實例化一個類的時候?qū)崿F(xiàn)一個新的接口。
最后,注意一個實例的繼承結(jié)構(gòu)會因為混合了繼承自其它Traits 的Traits 而變得復(fù)雜。我們會在《第7章 - Scala 對象系統(tǒng)》的“對象層次結(jié)構(gòu)的線性化”章節(jié)來討論這些層次結(jié)構(gòu)的細(xì)節(jié)。
可堆疊Traits
我們可以通過一系列精煉來提高我們工作的可重用性,使得我們可以更容易地同時使用一個以上的Trait,例如,“堆疊”它們。
首先,讓我們來引入一個新的Trait,Clickable,一個任意構(gòu)件響應(yīng)點擊事件的抽象。
// code-examples/Traits/ui2/clickable.scala package ui2 Trait Clickable { def click() }
注意
我們由一個新的包,ui2 開始,這樣我們可以更容易的區(qū)分開下載下來的新舊代碼。
Clickable Trait 看上去就像一個Java 接口;它是完全抽象的。它定義了一個單獨的抽象的方法,click。因為它沒有函數(shù)體,所以稱之為抽象。如果Clickable 是一個類的話,我們則應(yīng)該在class 關(guān)鍵字前面加上abstract 關(guān)鍵字。但是這對于Trait 來說不是必須的。
這里是重構(gòu)過的按鈕類,使用了這個Trait。
// code-examples/Traits/ui2/button.scala package ui2 import ui.Widget class Button(val label: String) extends Widget with Clickable { def click() = { // Logic to give the appearance of clicking a button... } }
這段代碼就像Java 實現(xiàn)一個Clickable 接口一樣。
當(dāng)我們在前面定義ObservableButton 的時候(在“混合Traits”章節(jié)),我們重寫了Button.click 來通知觀察者。而我們在聲明observableButton 為一個按鈕實例的時候,我們直接混合了Subject Trait,它重復(fù)了ButtonObserverAnonSpec 的邏輯。讓我們來消除這個重復(fù)。
當(dāng)我們開始用這種方式重構(gòu)代碼的時候,我們意識到我們實際上不關(guān)心“觀察”按鈕;我們關(guān)心的是“觀察”點擊。這里是一個單一的專注于觀察點擊的Trait。
// code-examples/Traits/ui2/observable-clicks.scala package ui2 import observer._ Trait ObservableClicks extends Clickable with Subject { abstract override def click() = { super.click() notifyObservers } }
ObservableClick Trait 繼承自Clickable,并且混合了Subject。然后它重寫了click 方法,像在“混合Traits”章節(jié)重寫的方法幾乎一樣的實現(xiàn)。最重要的區(qū)別就是abstract 關(guān)鍵字。
仔細(xì)看這個方法。它調(diào)用了super.click(),但是這里super 是什么意思?在這里,它看上去只能是聲明了但是沒有定義click 方法的Clickable,或者Subject,它并沒有click 方法。所以,super 的身份還不一定,至少現(xiàn)在還不一定。
實際上,super 會在這個Trait 混入一個定義了具體click 方法的實例的時候被綁定。這樣,我們需要在ObservableClicks.click 前加上abstract 關(guān)鍵字來告訴編譯器(或者讀者)click 還沒有被完全實現(xiàn),即使ObservableClicks.click 有一個函數(shù)體。
注意
除了聲明抽象類,abstract 關(guān)鍵字只在Trait 的方法有函數(shù)體,但是調(diào)用了在父類沒有具體實現(xiàn)的super 的方法的時候需要。
讓我們在Specs 測試中和Button 類以及它的具體click 方法一起使用這個Trait。
// code-examples/Traits/ui2/button-clickable-observer-spec.scala package ui2 import org.specs._ import observer._ import ui.ButtonCountObserver object ButtonClickableObserverSpec extends Specification { "A Button Observer" should { "observe button clicks" in { val observableButton = new Button("Okay") with ObservableClicks val buttonClickCountObserver = new ButtonCountObserver observableButton.addObserver(buttonClickCountObserver) for (i <- 1 to 3) observableButton.click() buttonClickCountObserver.count mustEqual 3 } } }
把這段代碼和ButtonObserverAnonSpec 比較。我們初始化了一個Button,混入ObservableClicks Trait,但是這次我們不需要重寫click 方法。所以,這個Button 的使用者不用惦記著重寫一個合適的click。這部分工作已經(jīng)由ObservableClicks 完成。想要的行為在我們需要的時候被聲明性地組合到代碼里去。
讓我們再來加入一個Trait。JavaBeans 規(guī)范[JavaBeanSpec] 有“可否決”事件的概念,是說JavaBean 修改的監(jiān)聽者可以否決修改。讓我們來用Trait 來實現(xiàn)一個類似的機(jī)制,用來否決一系列的點擊。
// code-examples/Traits/ui2/vetoable-clicks.scala package ui2 import observer._ Trait VetoableClicks extends Clickable { val maxAllowed = 1 // default private var count = 0 abstract override def click() = { if (count < maxAllowed) { count += 1 super.click() } } }
再一次,我們重寫了click 方法。和以前一樣,必須聲明這個重寫是抽象的。允許點擊的數(shù)目默認(rèn)值是1。你可能想知道這里的默認(rèn)是什么?這個字段不是被聲明為val 嗎? 我們沒有聲明構(gòu)造函數(shù)用其它值來初始化它。我們會在《第6章 - Scala 高級面向?qū)ο缶幊獭返摹敝貙戭惡蚑raits 的成員“ 重溫這個問題。
這個Trait 還聲明了一個count 變量來記錄我們觀察到的點擊。它被定義為private (私有的),所以它對于Trait 外部的域來說是不可見的(參見《第5章 - Scala 基礎(chǔ)面向?qū)ο缶幊獭返摹笨梢娪蛞?guī)則“)。被重寫的click 方法會增加count。它只在count 小于等于maxAllowed 數(shù)目的時候調(diào)用super.click()。
這里是展示ObservableClicks 和VetoableClicks 一起工作的Specs 對象。注意每一個Trait 都需要一個單獨的with 關(guān)鍵字,和Java 對于implements 指令只要一個關(guān)鍵字和用逗號隔開名稱的實現(xiàn)方式不一樣。
// code-examples/Traits/ui2/button-clickable-observer-vetoable-spec.scala package ui2 import org.specs._ import observer._ import ui.ButtonCountObserver object ButtonClickableObserverVetoableSpec extends Specification { "A Button Observer with Vetoable Clicks" should { "observe only the first button click" in { val observableButton = new Button("Okay") with ObservableClicks with VetoableClicks val buttonClickCountObserver = new ButtonCountObserver observableButton.addObserver(buttonClickCountObserver) for (i <- 1 to 3) observableButton.click() buttonClickCountObserver.count mustEqual 1 } } }
觀察者的計數(shù)應(yīng)該是1。observableButton 有下面的語句聲明,
new Button("Okay") with ObservableClicks with VetoableClicks
我們可以推斷VetoableClicks 重寫的click 在ObservableClicks 重寫的click 之前被調(diào)用。大概地講,因為我們的匿名類沒有定義自己的click,所以這個方法會按照聲明從右到左開始尋找。實際上比這個更復(fù)雜,我們會在《第7章 - Scala 對象系統(tǒng)》的”對象結(jié)構(gòu)的線性化“ 章節(jié)討論。
同時,如果我們把使用Trait 的順序反過來會發(fā)生什么呢?
// code-examples/Traits/ui2/button-vetoable-clickable-observer-spec.scala package ui2 import org.specs._ import observer._ import ui.ButtonCountObserver object ButtonVetoableClickableObserverSpec extends Specification { "A Vetoable Button with Click Observer" should { "observe all the button clicks, even when some are vetoed" in { val observableButton = new Button("Okay") with VetoableClicks with ObservableClicks val buttonClickCountObserver = new ButtonCountObserver observableButton.addObserver(buttonClickCountObserver) for (i <- 1 to 3) observableButton.click() buttonClickCountObserver.count mustEqual 3 } } }
現(xiàn)在觀察者的計數(shù)應(yīng)該是3。ObservableClicks 現(xiàn)在比VetoableClicks 擁有更高的優(yōu)先級,所以點擊的計數(shù)會增加,即使有些點擊在接下來的動作中被否決!
所以,為了防止Trait 之間互相影響導(dǎo)致不可預(yù)料的后果,聲明的順序很重要。也許另外一個教訓(xùn)是,把對象分拆成太多細(xì)密的Traits 可能會值得你代碼的執(zhí)行變得復(fù)雜費解。
把你的程序分割成小的,個所有長的Trait 是個創(chuàng)建可重用,可伸縮的抽象和”組件“的強(qiáng)大方式。復(fù)雜的行為可以通過聲明Trait 的組合來完成。我們會在《第13章 - 應(yīng)用程序設(shè)計》的“可伸縮的抽象”中更多地探索這個概念。
構(gòu)造Traits
Traits 不支持輔助構(gòu)造函數(shù),它們也不支持在主構(gòu)造函數(shù),Trait 的主體里的參數(shù)列表。Traits 可以繼承類或者其它Trait。然而,因為它們不能給父類的構(gòu)造函數(shù)傳遞參數(shù)(哪怕是字面值),所以Traits 只能繼承有無參數(shù)的主/副構(gòu)造函數(shù)的類。
然而,不像類,Trait 的主體在每次使用Trait 創(chuàng)建一個實例的時候都會被執(zhí)行,正如下面的腳本所演示。
// code-examples/Traits/Trait-construction-script.scala Trait T1 { println( " in T1: x = " + x ) val x=1 println( " in T1: x = " + x ) } Trait T2 { println( " in T2: y = " + y ) val y="T2" println( " in T2: y = " + y ) } class Base12 { println( " in Base12: b = " + b ) val b="Base12" println( " in Base12: b = " + b ) } class C12 extends Base12 with T1 with T2 { println( " in C12: c = " + c ) val c="C12" println( " in C12: c = " + c ) } println( "Creating C12:" ) new C12println( "After Creating C12" )
用scala 命令運(yùn)行這段腳本會得到以下輸出。
Creating C12: in Base12: b = null in Base12: b = Base12 in T1: x = 0 in T1: x = 1 in T2: y = null in T2: y = T2 in C12: c = null in C12: c = C12 After Creating C12
注意類和Trait 構(gòu)造函數(shù)的調(diào)用順序。因為C12 的聲明繼承自Base12 with T1 with T2,這個類結(jié)構(gòu)的構(gòu)造順序是從左到右的,從基類Base12 開始,接著是Traits T1 和T2,最后是C12 的構(gòu)造主體。(對于構(gòu)造任意復(fù)雜的結(jié)構(gòu),參見《第7章 - Scala 對象系統(tǒng)》的“對象結(jié)構(gòu)的線性化”章節(jié)。)
所以,雖然你不能傳遞構(gòu)造參數(shù)給Trait,你可以用默認(rèn)值初始化字段,或讓它們繼續(xù)抽象。我們實際上在前面的Subject Trait 中見過, Subject.observers 字段被初始化為一個空列表。
如果Trait 的一個具體字段沒有合適的默認(rèn)值,那么就沒有一個“萬無一失”的方式來初始化這個值了。所有的其它方法都需要這個Trait 的用戶的一些特別步驟,這很容易發(fā)生錯誤,因為他們可能會做錯甚至忘記去做。也許這個字段應(yīng)該繼續(xù)作為抽象字段,這樣類和其它Trait 使用它的時候會被強(qiáng)制定義一個合適的值。我們會在《第6章 - Scala 高級面向?qū)ο缶幊獭分性敿?xì)討論重寫抽象和具體成員。
另外一個解決方案是把這個字段轉(zhuǎn)移到一個單獨的類中,這樣構(gòu)造過程可以保證用戶可以提供正確的初始化數(shù)據(jù)。這樣也許應(yīng)該說這整個Trait 實際上應(yīng)該是一個類,這樣你才能定義一個構(gòu)造函數(shù)來初始化這個字段。
類還是Trait?
當(dāng)我們考慮是否一個“概念”應(yīng)該成為一個Trait 或者一個類的時候,記住作為混入的Trait 對于“附屬”行為來說最有意義。如果你發(fā)現(xiàn)某一個Trait 經(jīng)常作為其它類的父類來用,導(dǎo)致子類會有像父Trait 那樣的行為,那么考慮把它定義為一個類吧,讓這段邏輯關(guān)系更加清晰。(我們說像。。。的行為,而不是是。。。,因為前者是繼承更精確的定義,基于Liskov Substitution Principle -- 例如參見 [Martin2003]。)
提示
在Trait 里避免不能用合適的默認(rèn)值初始化的具體字段。使用抽象字段,或者把這個Trait 轉(zhuǎn)換成一個有構(gòu)造函數(shù)的類。當(dāng)然,無狀態(tài)Trait 沒有初始化的問題。
一個實例應(yīng)該從構(gòu)造過程結(jié)束開始,永遠(yuǎn)都在一個已知的有效的狀態(tài)下,這是優(yōu)秀的面向?qū)ο笤O(shè)計的基本原則。
以上是“Scala中Traits功能是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。