成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Scala編程指南 了解Traits功能

開(kāi)發(fā) 后端
本文為《Scala編程指南》的第四部分,將介紹Traits功能。Scala 是一種基于JVM,集合了面向?qū)ο缶幊毯秃瘮?shù)式編程優(yōu)點(diǎn)的高級(jí)程序設(shè)計(jì)語(yǔ)言。

近日,Scala的創(chuàng)始人發(fā)表了“Scala is for good programmers(Scala語(yǔ)言是給專(zhuān)家級(jí)程序員的)”這樣的言論!Scala在現(xiàn)在這個(gè)階段并不需要適合一般的Java程序員。要吸引的是一些專(zhuān)家級(jí)的程序員——優(yōu)秀的程序員。目標(biāo)是使他們工作起來(lái)比使用Java更有效率。只有會(huì)出現(xiàn)足夠多的教育示范材料和足夠好的開(kāi)發(fā)工具時(shí),Scala對(duì)廣大的普通開(kāi)發(fā)人員才具有吸引力。但是Scala語(yǔ)言什么呢?

Scala編程指南》系列文章將會(huì)詳細(xì)介紹Scala語(yǔ)言。本文為《Scala編程指南》系列的第四章,將介紹Traits功能。

Traits 介紹

在我們深入面向?qū)ο缶幊讨埃覀冞€需要了解Scala 一個(gè)特性:Traits。要了解這個(gè)功能需要一點(diǎn)歷史知識(shí)。

在Java 中,一個(gè)類(lèi)可以實(shí)現(xiàn)任意數(shù)量的接口。這個(gè)模型在聲明一個(gè)類(lèi)實(shí)現(xiàn)多個(gè)抽象的時(shí)候非常有用。不幸的是,它也有一個(gè)主要缺點(diǎn)。

對(duì)于許多接口,大多數(shù)功能都可以用對(duì)于所有使用這個(gè)接口的類(lèi)都有效的“樣板”代碼來(lái)實(shí)現(xiàn)。Java 沒(méi)有提供一個(gè)內(nèi)置機(jī)制來(lái)定義和使用這些可重用代碼。相反的,Java 程序員必須使用一個(gè)特別的轉(zhuǎn)換來(lái)重用一個(gè)已知接口的實(shí)現(xiàn)。在最壞的情況下,程序員必須復(fù)制粘貼同樣的代碼到不同的類(lèi)中去。

通常,一個(gè)接口的實(shí)現(xiàn)擁有和該實(shí)例的其它成員無(wú)關(guān)(正交)的成員。術(shù)語(yǔ)mixin (混合)通常被用來(lái)指實(shí)例中這些專(zhuān)注的,潛在可被重用的,并且可以獨(dú)立維護(hù)的代碼。

來(lái)看一下下面這個(gè)圖形用戶(hù)接口的按鈕的代碼,它對(duì)單擊事件使用了回調(diào)。

  1. // code-examples/Traits/ui/button-callbacks.scala  
  2. package ui  
  3. class ButtonWithCallbacks(val label: String,  
  4.     val clickedCallbacks: List[() => Unit]) extends Widget {  
  5.  
  6.   require(clickedCallbacks != null, "Callback list can't be null!")  
  7.  
  8.   def this(label: String, clickedCallback: () => Unit) =  
  9.     this(label, List(clickedCallback))  
  10.  
  11.   def this(label: String) = {  
  12.     this(label, Nil)  
  13.     println("Warning: button has no click callbacks!")  
  14.   }  
  15.  
  16.   def click() = {  
  17.     // ... logic to give the appearance of clicking a physical button ...  
  18.     clickedCallbacks.foreach(f => f())  
  19.   }  
  20. }  

這里發(fā)生了很多事情。主構(gòu)造函數(shù)接受一個(gè)label(標(biāo)簽)參數(shù)和一個(gè)callbacks(回調(diào))的list(列表),這些回調(diào)函數(shù)會(huì)在按鈕的click 方法被調(diào)用時(shí)被調(diào)用。我們會(huì)在《第5章 - Scala 基礎(chǔ)面向?qū)ο缶幊獭分衼?lái)探索這個(gè)類(lèi)的更多細(xì)節(jié)?,F(xiàn)在,我們希望專(zhuān)注在一個(gè)特別的問(wèn)題上。ButtonWithCallbacks 不僅處理按鈕的一些本質(zhì)行為(比如單擊),它同時(shí)還通過(guò)調(diào)用回調(diào)函數(shù)處理單擊事件的通知。這違反了單職原則[Martin2003],這是分隔職能的一種設(shè)計(jì)方法。我們可以把按鈕類(lèi)的邏輯從回調(diào)邏輯中分離出來(lái),這樣每一個(gè)邏輯組件變得更加簡(jiǎn)單,更模塊化,更可重用化。這個(gè)回調(diào)邏輯就是mixin 的一個(gè)不錯(cuò)例子。

這樣的分離在Java 中很難做,即使我們定義了具有回調(diào)行為的接口,我們?nèi)匀恍枰陬?lèi)中集成實(shí)現(xiàn)代碼,降低模塊性。唯一的其它方法則是使用特定的工具比如面向方面編程(Aspect-Oriented Programming,AOP,參見(jiàn)[AOSD]),一個(gè)實(shí)現(xiàn)是AspectJ,Java 的一個(gè)擴(kuò)展。AOP 主要被設(shè)計(jì)用來(lái)分離在應(yīng)用程序中重復(fù)出現(xiàn)的普遍問(wèn)題的實(shí)現(xiàn)。它設(shè)法模塊化這些關(guān)鍵點(diǎn),但是也允許設(shè)計(jì)良好的和其它關(guān)鍵點(diǎn)行為的“混合”,包括應(yīng)用程序的核心域邏輯,不管是在編譯時(shí)還是運(yùn)行時(shí)。

作為混合體的Traits

Scala 提供了完整的混合(mixin)解決方案,稱(chēng)為T(mén)raits。在我們的例子里,我們可以定義回調(diào)的抽象為一個(gè)Trait,就像一個(gè)Java 接口一樣,但是我們可以實(shí)現(xiàn)這些Trait (或者繼承的Trait)的抽象。我們可以定義混合了Trait 的類(lèi),大致上很像實(shí)現(xiàn)Java 的一個(gè)接口。不過(guò),在Scala 你甚至可以在我們創(chuàng)建實(shí)例的時(shí)候混合Traits。也就是說(shuō),我們不必首先聲明一個(gè)混合了所有我們所需要的Trait 的類(lèi)。所以,Scala Traits 在保留分離關(guān)鍵點(diǎn)的同時(shí)給了我們按需整合行為的能力。

如果你來(lái)自于Java 編程世界,你可以認(rèn)為T(mén)raits 是有選擇地實(shí)現(xiàn)了的接口。其它語(yǔ)言提供了類(lèi)似Trait 的結(jié)構(gòu),比如Ruby 中的模塊(modules)。

讓我們對(duì)按鈕的邏輯來(lái)使用Trait,從而分離回調(diào)的處理。我們會(huì)推廣一下我們的實(shí)現(xiàn)?;卣{(diào)其實(shí)是觀察者模式[GOF1995] 的一個(gè)特例。所以,讓我們創(chuàng)建一個(gè)Trait 來(lái)實(shí)現(xiàn)這個(gè)模式,然后用它來(lái)處理回調(diào)行為。為了簡(jiǎn)單化,我們從一個(gè)單獨(dú)的計(jì)算按鈕被按次數(shù)的回調(diào)開(kāi)始。

首先,讓我們定義一個(gè)簡(jiǎn)單的Button 類(lèi)。

  1. // code-examples/Traits/ui/button.scala  
  2. package ui  
  3. class Button(val label: String) extends Widget {  
  4.   def click() = {  
  5.     // Logic to give the appearance of clicking a button...  
  6.   }  
  7. }  
  8.  

這里是它的父類(lèi),Widget。

  1. // code-examples/Traits/ui/widget.scala  
  2. package ui  
  3. abstract class Widget  
  4.  

管理回調(diào)的邏輯(比如,clickedCallbacks 列表)被省略了,兩個(gè)主要構(gòu)造函數(shù)也是。只有按鈕的label 字段和click 方法被保留了下來(lái)。這個(gè)click 方法現(xiàn)在只關(guān)心一個(gè)“物理上的” 按鈕被單擊時(shí)候的可見(jiàn)表現(xiàn)。按鈕只有一個(gè)關(guān)心的東西,就是處理作為一個(gè)按鈕的本質(zhì)行為。

這里是一個(gè)實(shí)現(xiàn)了觀察者模式邏輯的Trait。

  1. // code-examples/Traits/observer/observer.scala  
  2. package observer  
  3. Trait Subject {  
  4.   type Observer = { def receiveUpdate(subject: Any) }  
  5.   private var observers = List[Observer]()  
  6.   def addObserver(observer:Observer) = observers ::observer 
  7.   def notifyObservers = observers foreach (_.receiveUpdate(this))  
  8. }  
  9.  

除了Trait 關(guān)鍵字,Subject 看起來(lái)就像一個(gè)普通的類(lèi)。Subject 定義了所有它聲明的成員。Traits 可以聲明抽象成員,具體成員,或者兩者皆有,就像類(lèi)所能做的一樣(參見(jiàn)《第6章 - Scala 高級(jí)面向?qū)ο缶幊獭返?ldquo;重寫(xiě)類(lèi)和Traits 的成員”章節(jié)獲取更多信息)。而且,Traits 能包含嵌套的Trait 和類(lèi)定義,類(lèi)也能包含嵌套的Trait 定義。

第一行定義了Observer 類(lèi)型。這是一個(gè)結(jié)構(gòu)類(lèi)型,形式是 { def receiveUpdate(subject:Any) }。結(jié)構(gòu)類(lèi)型僅制定了子類(lèi)型必須支持的結(jié)構(gòu);你可以把它們看作是“匿名”類(lèi)型。

在這個(gè)例子里,這個(gè)結(jié)構(gòu)類(lèi)型由一個(gè)有著特定簽名的方法定義。任何有這個(gè)簽名的方法的類(lèi)型都可以被用作為一個(gè)observer(觀察者)。我們會(huì)在《第12章 - Scala 類(lèi)型系統(tǒng)》學(xué)習(xí)更多有關(guān)結(jié)構(gòu)類(lèi)型的內(nèi)容。如果你想知道為什么我們不把Subject 作為參數(shù),而是Any。我們會(huì)在《第13章 - 應(yīng)用程序設(shè)計(jì)》的“自我類(lèi)型注解和抽象類(lèi)型成員”章節(jié)來(lái)重習(xí)這個(gè)問(wèn)題。

我們所需要注意的最主要的是這樣的結(jié)構(gòu)類(lèi)型如何最小化了Subject Trait 和任何潛在的Trait 用戶(hù)之間的耦合。

注意

Subject 仍然通過(guò)結(jié)構(gòu)類(lèi)型和Observer 中的方法名稱(chēng)耦合在一起,例如,名為receiveUpdate 的方法。我們有幾種方法來(lái)省去這剩下的耦合。我們會(huì)在《第6章 - Scala 高級(jí)面向?qū)ο缶幊獭分械?ldquo;重寫(xiě)抽象類(lèi)型”章節(jié)看到如何做到這一點(diǎn)。

下面,我們聲明了一系列觀察者。我們定義了一個(gè)var,而不是val,因?yàn)長(zhǎng)ist 是不可變的。所以我們必須在一個(gè)觀察者通過(guò)addObserver 方法被添加時(shí)創(chuàng)建一個(gè)新的列表。

我們會(huì)在《第7章 - Scala 對(duì)象系統(tǒng)》的“Scala 類(lèi)型結(jié)構(gòu)”章節(jié)和《第8章 - Scala 函數(shù)式編程》中討論更多有關(guān)List 的細(xì)節(jié)?,F(xiàn)在,注意addObserver 使用了列表的cons “操作符”方法(::)來(lái)在一個(gè)列表前面加入一個(gè)觀察者。Scala 編譯器會(huì)聰明地把下面的語(yǔ)句,

  1. observers ::observer 
  2.  

轉(zhuǎn)換成如下語(yǔ)句,

  1. observerobservers = observer :: observers  
  2.  

注意我們寫(xiě)了observer:: observers,把已存的observers 列表放到了右邊。回憶一下,所有的以: 結(jié)尾的方法是右綁定的。所以,前一個(gè)語(yǔ)句和下面的語(yǔ)句等價(jià)。

  1. observersobservers = observers.::(observer)  
  2.  

notifyObservers 方法遍歷所有的觀察者,使用foreach 方法,然后對(duì)每一個(gè)觀察者調(diào)用receiveUpdate 方法。(注意我們使用了“插入”操作符標(biāo)記法而不是observers.foreach。)我們使用占位符'_' 來(lái)縮短下面的表達(dá)式,

  1. (obs) => obs.receiveUpdate(this)  
  2.  

為這樣的表達(dá)式,

  1. _.receiveUpdate(this)  
  2.  

這個(gè)表達(dá)式實(shí)際上是一個(gè)“匿名函數(shù)”的函數(shù)體,在Scala 中稱(chēng)為字面函數(shù)。這和其它語(yǔ)言中的Lambda 表達(dá)式或類(lèi)似結(jié)構(gòu)相似。字面函數(shù)和閉包相關(guān)的概念會(huì)在《第8章 - Scala 函數(shù)式編程》的“字面函數(shù)和閉包”章節(jié)中被討論。

在Java 中,foreach 方法很可能會(huì)接受一個(gè)接口,你可能會(huì)傳遞一個(gè)實(shí)現(xiàn)了該接口的類(lèi)的實(shí)例。(比如,典型的Comparable 使用的方法)。

在Scala 中,List[A].foreach 方法期待的參數(shù)類(lèi)型為(A)=>Unit,這是一個(gè)函數(shù),接受一個(gè)A 類(lèi)型的參數(shù),而A 標(biāo)識(shí)了列表的元素的類(lèi)型(在這個(gè)例子中是Observer),然后返回Unit(和Java 的void 一樣)。

注意

我們?cè)谶@個(gè)例子里選擇使用一個(gè)var 來(lái)表示不可變的觀察者的List。我們也可以使用val 和一個(gè)可變的類(lèi)型,比如ListBuffer。這個(gè)選擇會(huì)在一個(gè)真實(shí)的應(yīng)用程序中顯得更加合理,但是我們希望避免介紹新的類(lèi)來(lái)分散我們的注意。

再一次的,我們從一個(gè)小例子里學(xué)習(xí)了許多Scala 的知識(shí)?,F(xiàn)在,讓我們來(lái)用一用我們的Subject Trait。這里有一個(gè)ObservableButton,它繼承了Button,混合了Subject。

  1. // code-examples/Traits/ui/observable-button.scala  
  2. package ui  
  3. import observer._  
  4. class ObservableButton(name: String) extends Button(name) with Subject {  
  5.   override def click() = {  
  6.     super.click()  
  7.     notifyObservers  
  8.   }  
  9. }  
  10.  

我們從導(dǎo)入observer 包的所有東西開(kāi)始,使用'_' 通配符。實(shí)際上,我們?cè)谶@個(gè)包中只定義了Subject Trait。

新的類(lèi)使用了with 關(guān)鍵字把Subject Trait 加到類(lèi)中。ObserverButton 重寫(xiě)了click 方法。使用super 關(guān)鍵字(參見(jiàn)《第6章 - Scala 高級(jí)面向?qū)ο缶幊獭返?ldquo;重寫(xiě)抽象和具體方法”章節(jié)),它首先調(diào)用了“父類(lèi)”的方法,Button.click,然后它通知觀察者。因?yàn)樾碌腸lick 方法重寫(xiě)了Button 的具體實(shí)現(xiàn),必須加上override 關(guān)鍵字。

with 關(guān)鍵字和Java 的對(duì)接口使用的implement 關(guān)鍵字類(lèi)似。你可以是頂任意多的Traits,每一個(gè)都必須有with 關(guān)鍵字。

一個(gè)類(lèi)可以繼承一個(gè)Trait,一個(gè)Trait 也可以繼承一個(gè)類(lèi)。實(shí)際上,我們的Widget 類(lèi)也可以被聲明為一個(gè)Trait。

注意

如果你定義一個(gè)類(lèi)使用一個(gè)或多個(gè)Traits,而它又不繼承任何類(lèi),你必須對(duì)第一個(gè)列出的Trait 使用extends 關(guān)鍵字。

如果你不對(duì)第一個(gè)Trait 使用extends,例如寫(xiě)成這樣。

  1. // ERROR:  
  2. class ObservableButton(name: String) with Button(name) with Subject {...}  
  3.  

你會(huì)獲得如下錯(cuò)誤。

  1. ... error: ';' expected but 'with' found.  
  2.        class ObservableButton(name: String) with Button(name) with Subject {...}  
  3.                                             ^  
  4.  

這個(gè)錯(cuò)誤實(shí)際上應(yīng)該說(shuō)“with found,but extends expected。”(發(fā)現(xiàn)with 關(guān)鍵字,但是期望一個(gè)extends。)

要演示這部分代碼,讓我們從一個(gè)觀察按鈕點(diǎn)擊和記錄點(diǎn)擊數(shù)目的類(lèi)開(kāi)始。

  1. // code-examples/Traits/ui/button-count-observer.scala  
  2. package ui  
  3. import observer._  
  4. class ButtonCountObserver {  
  5.   var count = 0 
  6.   def receiveUpdate(subject: Any) = count += 1  
  7. }  
  8.  

左后,讓我們寫(xiě)一個(gè)測(cè)試來(lái)運(yùn)用所有的類(lèi)。我們會(huì)使用Specs 庫(kù)(在《第14章 - Scala 工具,庫(kù)和IDE 支持》的“Specs” 章節(jié)討論) 來(lái)寫(xiě)一個(gè)行為驅(qū)動(dòng)(【BDD】)的“規(guī)范”來(lái)測(cè)試組合后的Button 和Subject 類(lèi)型。

  1. // code-examples/Traits/ui/button-observer-spec.scala  
  2. package ui  
  3. import org.specs._  
  4. import observer._  
  5. object ButtonObserverSpec extends Specification {  
  6.   "A Button Observer" should {  
  7.     "observe button clicks" in {  
  8.       val observableButton = new ObservableButton("Okay")  
  9.       val buttonObserver = new ButtonCountObserver  
  10.       observableButton.addObserver(buttonObserver)  
  11.       for (i <- 1 to 3) observableButton.click()  
  12.       buttonObserver.count mustEqual 3  
  13.     }  
  14.   }  
  15. }  
  16.  

如果你從O'Reilly 網(wǎng)站下載了代碼例子,你可以按照README 文件的指示來(lái)編譯和運(yùn)行這個(gè)章節(jié)的例子。specs “目標(biāo)”的輸出應(yīng)該包含如下的內(nèi)容。

  1. Specification "ButtonCountObserverSpec"  
  2.   A Button Observer should  
  3.   + observe button clicks  
  4. Total for specification "ButtonCountObserverSpec":  
  5. Finished in 0 second, 10 ms  
  6. 1 example, 1 expectation, 0 failure, 0 error  
  7.  

注意字符串“A Button Observer Should”和“observe button clicks” 對(duì)應(yīng)了例子中的字符串。Specs 的輸出提供了一個(gè)漂亮的被測(cè)試的項(xiàng)目的需求,并假設(shè)為這些字符串做了合適的決定。

測(cè)試的主題創(chuàng)建了一個(gè)“Okay” ObservableButton 和一個(gè)ButtonCountObserver,把觀察者給了這個(gè)button。按鈕通過(guò)for 循環(huán)被按了3次。最后一行要求observer 的計(jì)數(shù)等于3。如果你習(xí)慣使用XUnit 風(fēng)格的TDD (測(cè)試驅(qū)動(dòng)開(kāi)發(fā))工具,例如JUnit 或者ScalaTest(參見(jiàn)《第14章 - Scala 工具,庫(kù)和IDE 支持》的“ScalaTest” 章節(jié)),那么最后一行等效于下面的JUnit 斷言。

  1. assertEquals(3, buttonObserver.count)  
  2.  

注意

Specs 庫(kù)(參見(jiàn)“Specs” 章節(jié)) 和ScalaTest 庫(kù)(參見(jiàn)“ScalaTest”章節(jié))都支持行為驅(qū)動(dòng)開(kāi)發(fā)[BDD],測(cè)試驅(qū)動(dòng)開(kāi)發(fā)[TDD] 的一種風(fēng)格,強(qiáng)調(diào)了測(cè)試的“規(guī)范”角色。

假設(shè)我們只需要一個(gè)ObservableButton 實(shí)例呢?我們實(shí)際上不用聲明一個(gè)繼承Subject 的Button 的子類(lèi)。我們可以在創(chuàng)建實(shí)例的時(shí)候加上Trait。

下一個(gè)例子展示了一個(gè)修訂的Specs 文件,它實(shí)例化了一個(gè)Button,混合了Subject 作為聲明的一部分。

  1. // code-examples/Traits/ui/button-observer-anon-spec.scala  
  2. package ui  
  3. import org.specs._  
  4. import observer._  
  5. object ButtonObserverAnonSpec extends Specification {  
  6.   "A Button Observer" should {  
  7.     "observe button clicks" in {  
  8.       val observableButton = new Button("Okay") with Subject {  
  9.         override def click() = {  
  10.           super.click()  
  11.           notifyObservers  
  12.         }  
  13.       }  
  14.       val buttonObserver = new ButtonCountObserver  
  15.       observableButton.addObserver(buttonObserver)  
  16.       for (i <- 1 to 3) observableButton.click()  
  17.       buttonObserver.count mustEqual 3  
  18.     }  
  19.   }  
  20. }  
  21.  

修訂過(guò)的observableButton 的聲明實(shí)際上創(chuàng)建了一個(gè)匿名類(lèi),并且像之前那樣重寫(xiě)了click 方法。和在Java 中創(chuàng)建匿名類(lèi)的主要區(qū)別是我們可以在過(guò)程中引入Trait。Java 不允許你在實(shí)例化一個(gè)類(lèi)的時(shí)候?qū)崿F(xiàn)一個(gè)新的接口。

最后,注意一個(gè)實(shí)例的繼承結(jié)構(gòu)會(huì)因?yàn)榛旌狭死^承自其它Traits 的Traits 而變得復(fù)雜。我們會(huì)在《第7章 - Scala 對(duì)象系統(tǒng)》的“對(duì)象層次結(jié)構(gòu)的線性化”章節(jié)來(lái)討論這些層次結(jié)構(gòu)的細(xì)節(jié)。

#p#

可堆疊Traits

我們可以通過(guò)一系列精煉來(lái)提高我們工作的可重用性,使得我們可以更容易地同時(shí)使用一個(gè)以上的Trait,例如,“堆疊”它們。

首先,讓我們來(lái)引入一個(gè)新的Trait,Clickable,一個(gè)任意構(gòu)件響應(yīng)點(diǎn)擊事件的抽象。

  1. // code-examples/Traits/ui2/clickable.scala  
  2. package ui2  
  3. Trait Clickable {  
  4.   def click()  
  5. }  
  6.  

注意

我們由一個(gè)新的包,ui2 開(kāi)始,這樣我們可以更容易的區(qū)分開(kāi)下載下來(lái)的新舊代碼。

Clickable Trait 看上去就像一個(gè)Java 接口;它是完全抽象的。它定義了一個(gè)單獨(dú)的抽象的方法,click。因?yàn)樗鼪](méi)有函數(shù)體,所以稱(chēng)之為抽象。如果Clickable 是一個(gè)類(lèi)的話(huà),我們則應(yīng)該在class 關(guān)鍵字前面加上abstract 關(guān)鍵字。但是這對(duì)于Trait 來(lái)說(shuō)不是必須的。

這里是重構(gòu)過(guò)的按鈕類(lèi),使用了這個(gè)Trait。

  1. // code-examples/Traits/ui2/button.scala  
  2. package ui2  
  3. import ui.Widget  
  4. class Button(val label: String) extends Widget with Clickable {  
  5.   def click() = {  
  6.     // Logic to give the appearance of clicking a button...  
  7.   }  
  8. }  
  9.  

這段代碼就像Java 實(shí)現(xiàn)一個(gè)Clickable 接口一樣。

當(dāng)我們?cè)谇懊娑xObservableButton 的時(shí)候(在“混合Traits”章節(jié)),我們重寫(xiě)了Button.click 來(lái)通知觀察者。而我們?cè)诼暶鱫bservableButton 為一個(gè)按鈕實(shí)例的時(shí)候,我們直接混合了Subject Trait,它重復(fù)了ButtonObserverAnonSpec 的邏輯。讓我們來(lái)消除這個(gè)重復(fù)。

當(dāng)我們開(kāi)始用這種方式重構(gòu)代碼的時(shí)候,我們意識(shí)到我們實(shí)際上不關(guān)心“觀察”按鈕;我們關(guān)心的是“觀察”點(diǎn)擊。這里是一個(gè)單一的專(zhuān)注于觀察點(diǎn)擊的Trait。

  1. // code-examples/Traits/ui2/observable-clicks.scala  
  2. package ui2  
  3. import observer._  
  4. Trait ObservableClicks extends Clickable with Subject {  
  5.   abstract override def click() = {  
  6.     super.click()  
  7.     notifyObservers  
  8.   }  
  9. }  
  10.  

ObservableClick Trait 繼承自Clickable,并且混合了Subject。然后它重寫(xiě)了click 方法,像在“混合Traits”章節(jié)重寫(xiě)的方法幾乎一樣的實(shí)現(xiàn)。最重要的區(qū)別就是abstract 關(guān)鍵字。

仔細(xì)看這個(gè)方法。它調(diào)用了super.click(),但是這里super 是什么意思?在這里,它看上去只能是聲明了但是沒(méi)有定義click 方法的Clickable,或者Subject,它并沒(méi)有click 方法。所以,super 的身份還不一定,至少現(xiàn)在還不一定。

實(shí)際上,super 會(huì)在這個(gè)Trait 混入一個(gè)定義了具體click 方法的實(shí)例的時(shí)候被綁定。這樣,我們需要在ObservableClicks.click 前加上abstract 關(guān)鍵字來(lái)告訴編譯器(或者讀者)click 還沒(méi)有被完全實(shí)現(xiàn),即使ObservableClicks.click 有一個(gè)函數(shù)體。

注意

除了聲明抽象類(lèi),abstract 關(guān)鍵字只在Trait 的方法有函數(shù)體,但是調(diào)用了在父類(lèi)沒(méi)有具體實(shí)現(xiàn)的super 的方法的時(shí)候需要。

讓我們?cè)赟pecs 測(cè)試中和Button 類(lèi)以及它的具體click 方法一起使用這個(gè)Trait。

  1. // code-examples/Traits/ui2/button-clickable-observer-spec.scala  
  2. package ui2  
  3. import org.specs._  
  4. import observer._  
  5. import ui.ButtonCountObserver  
  6. object ButtonClickableObserverSpec extends Specification {  
  7.   "A Button Observer" should {  
  8.     "observe button clicks" in {  
  9.       val observableButton = new Button("Okay") with ObservableClicks  
  10.       val buttonClickCountObserver = new ButtonCountObserver  
  11.       observableButton.addObserver(buttonClickCountObserver)  
  12.       for (i <- 1 to 3) observableButton.click()  
  13.       buttonClickCountObserver.count mustEqual 3  
  14.     }  
  15.   }  
  16. }  
  17.  

把這段代碼和ButtonObserverAnonSpec 比較。我們初始化了一個(gè)Button,混入ObservableClicks Trait,但是這次我們不需要重寫(xiě)click 方法。所以,這個(gè)Button 的使用者不用惦記著重寫(xiě)一個(gè)合適的click。這部分工作已經(jīng)由ObservableClicks 完成。想要的行為在我們需要的時(shí)候被聲明性地組合到代碼里去。

讓我們?cè)賮?lái)加入一個(gè)Trait。JavaBeans 規(guī)范[JavaBeanSpec] 有“可否決”事件的概念,是說(shuō)JavaBean 修改的監(jiān)聽(tīng)者可以否決修改。讓我們來(lái)用Trait 來(lái)實(shí)現(xiàn)一個(gè)類(lèi)似的機(jī)制,用來(lái)否決一系列的點(diǎn)擊。

  1. // code-examples/Traits/ui2/vetoable-clicks.scala  
  2. package ui2  
  3. import observer._  
  4. Trait VetoableClicks extends Clickable {  
  5.   val maxAllowed = 1  // default  
  6.   private var count = 0 
  7.   abstract override def click() = {  
  8.     if (count < maxAllowed) {  
  9.       count += 1  
  10.       super.click()  
  11.     }  
  12.   }  
  13. }  
  14.  
  15.     
  16.  

再一次,我們重寫(xiě)了click 方法。和以前一樣,必須聲明這個(gè)重寫(xiě)是抽象的。允許點(diǎn)擊的數(shù)目默認(rèn)值是1。你可能想知道這里的默認(rèn)是什么?這個(gè)字段不是被聲明為val 嗎? 我們沒(méi)有聲明構(gòu)造函數(shù)用其它值來(lái)初始化它。我們會(huì)在《第6章 - Scala 高級(jí)面向?qū)ο缶幊獭返?rdquo;重寫(xiě)類(lèi)和Traits 的成員“ 重溫這個(gè)問(wèn)題。

這個(gè)Trait 還聲明了一個(gè)count 變量來(lái)記錄我們觀察到的點(diǎn)擊。它被定義為private (私有的),所以它對(duì)于Trait 外部的域來(lái)說(shuō)是不可見(jiàn)的(參見(jiàn)《第5章 - Scala 基礎(chǔ)面向?qū)ο缶幊獭返?rdquo;可見(jiàn)域規(guī)則“)。被重寫(xiě)的click 方法會(huì)增加count。它只在count 小于等于maxAllowed 數(shù)目的時(shí)候調(diào)用super.click()。

這里是展示ObservableClicks 和VetoableClicks 一起工作的Specs 對(duì)象。注意每一個(gè)Trait 都需要一個(gè)單獨(dú)的with 關(guān)鍵字,和Java 對(duì)于implements 指令只要一個(gè)關(guān)鍵字和用逗號(hào)隔開(kāi)名稱(chēng)的實(shí)現(xiàn)方式不一樣。

  1. // code-examples/Traits/ui2/button-clickable-observer-vetoable-spec.scala  
  2. package ui2  
  3. import org.specs._  
  4. import observer._  
  5. import ui.ButtonCountObserver  
  6. object ButtonClickableObserverVetoableSpec extends Specification {  
  7.   "A Button Observer with Vetoable Clicks" should {  
  8.     "observe only the first button click" in {  
  9.       val observableButton =  
  10.           new Button("Okay") with ObservableClicks with VetoableClicks  
  11.       val buttonClickCountObserver = new ButtonCountObserver  
  12.       observableButton.addObserver(buttonClickCountObserver)  
  13.       for (i <- 1 to 3) observableButton.click()  
  14.       buttonClickCountObserver.count mustEqual 1  
  15.     }  
  16.   }  
  17. }  
  18.  
  19.  
  20.  

觀察者的計(jì)數(shù)應(yīng)該是1。observableButton 有下面的語(yǔ)句聲明,

  1. new Button("Okay") with ObservableClicks with VetoableClicks  
  2.  

我們可以推斷VetoableClicks 重寫(xiě)的click 在ObservableClicks 重寫(xiě)的click 之前被調(diào)用。大概地講,因?yàn)槲覀兊哪涿?lèi)沒(méi)有定義自己的click,所以這個(gè)方法會(huì)按照聲明從右到左開(kāi)始尋找。實(shí)際上比這個(gè)更復(fù)雜,我們會(huì)在《第7章 - Scala 對(duì)象系統(tǒng)》的”對(duì)象結(jié)構(gòu)的線性化“ 章節(jié)討論。

同時(shí),如果我們把使用Trait 的順序反過(guò)來(lái)會(huì)發(fā)生什么呢?

  1. // code-examples/Traits/ui2/button-vetoable-clickable-observer-spec.scala  
  2. package ui2  
  3. import org.specs._  
  4. import observer._  
  5. import ui.ButtonCountObserver  
  6. object ButtonVetoableClickableObserverSpec extends Specification {  
  7.   "A Vetoable Button with Click Observer" should {  
  8.     "observe all the button clicks, even when some are vetoed" in {  
  9.       val observableButton =  
  10.           new Button("Okay") with VetoableClicks with ObservableClicks  
  11.       val buttonClickCountObserver = new ButtonCountObserver  
  12.       observableButton.addObserver(buttonClickCountObserver)  
  13.       for (i <- 1 to 3) observableButton.click()  
  14.       buttonClickCountObserver.count mustEqual 3  
  15.     }  
  16.   }  
  17. }  
  18.  

現(xiàn)在觀察者的計(jì)數(shù)應(yīng)該是3。ObservableClicks 現(xiàn)在比VetoableClicks 擁有更高的優(yōu)先級(jí),所以點(diǎn)擊的計(jì)數(shù)會(huì)增加,即使有些點(diǎn)擊在接下來(lái)的動(dòng)作中被否決!

所以,為了防止Trait 之間互相影響導(dǎo)致不可預(yù)料的后果,聲明的順序很重要。也許另外一個(gè)教訓(xùn)是,把對(duì)象分拆成太多細(xì)密的Traits 可能會(huì)值得你代碼的執(zhí)行變得復(fù)雜費(fèi)解。

把你的程序分割成小的,個(gè)所有長(zhǎng)的Trait 是個(gè)創(chuàng)建可重用,可伸縮的抽象和”組件“的強(qiáng)大方式。復(fù)雜的行為可以通過(guò)聲明Trait 的組合來(lái)完成。我們會(huì)在《第13章 - 應(yīng)用程序設(shè)計(jì)》的“可伸縮的抽象”中更多地探索這個(gè)概念。

構(gòu)造Traits

Traits 不支持輔助構(gòu)造函數(shù),它們也不支持在主構(gòu)造函數(shù),Trait 的主體里的參數(shù)列表。Traits 可以繼承類(lèi)或者其它Trait。然而,因?yàn)樗鼈儾荒芙o父類(lèi)的構(gòu)造函數(shù)傳遞參數(shù)(哪怕是字面值),所以Traits 只能繼承有無(wú)參數(shù)的主/副構(gòu)造函數(shù)的類(lèi)。

然而,不像類(lèi),Trait 的主體在每次使用Trait 創(chuàng)建一個(gè)實(shí)例的時(shí)候都會(huì)被執(zhí)行,正如下面的腳本所演示。

  1. // code-examples/Traits/Trait-construction-script.scala  
  2. Trait T1 {  
  3.   println( "  in T1: x = " + x )  
  4.   val x=1 
  5.   println( "  in T1: x = " + x )  
  6. }  
  7. Trait T2 {  
  8.   println( "  in T2: y = " + y )  
  9.   val y="T2" 
  10.   println( "  in T2: y = " + y )  
  11. }  
  12. class Base12 {  
  13.   println( "  in Base12: b = " + b )  
  14.   val b="Base12" 
  15.   println( "  in Base12: b = " + b )  
  16. }  
  17. class C12 extends Base12 with T1 with T2 {  
  18.   println( "  in C12: c = " + c )  
  19.   val c="C12" 
  20.   println( "  in C12: c = " + c )  
  21. }  
  22. println( "Creating C12:" )  
  23. new C12println( "After Creating C12" )  
  24.  

用scala 命令運(yùn)行這段腳本會(huì)得到以下輸出。

  1. Creating C12:  
  2.   in Base12: b = null 
  3.   in Base12: b = Base12 
  4.   in T1: x = 0 
  5.   in T1: x = 1 
  6.   in T2: y = null 
  7.   in T2: y = T2 
  8.   in C12: c = null 
  9.   in C12: c = C12 
  10. After Creating C12  
  11.  
  12.     
  13.  

注意類(lèi)和Trait 構(gòu)造函數(shù)的調(diào)用順序。因?yàn)镃12 的聲明繼承自Base12 with T1 with T2,這個(gè)類(lèi)結(jié)構(gòu)的構(gòu)造順序是從左到右的,從基類(lèi)Base12 開(kāi)始,接著是Traits T1 和T2,最后是C12 的構(gòu)造主體。(對(duì)于構(gòu)造任意復(fù)雜的結(jié)構(gòu),參見(jiàn)《第7章 - Scala 對(duì)象系統(tǒng)》的“對(duì)象結(jié)構(gòu)的線性化”章節(jié)。)

所以,雖然你不能傳遞構(gòu)造參數(shù)給Trait,你可以用默認(rèn)值初始化字段,或讓它們繼續(xù)抽象。我們實(shí)際上在前面的Subject Trait 中見(jiàn)過(guò), Subject.observers 字段被初始化為一個(gè)空列表。

如果Trait 的一個(gè)具體字段沒(méi)有合適的默認(rèn)值,那么就沒(méi)有一個(gè)“萬(wàn)無(wú)一失”的方式來(lái)初始化這個(gè)值了。所有的其它方法都需要這個(gè)Trait 的用戶(hù)的一些特別步驟,這很容易發(fā)生錯(cuò)誤,因?yàn)樗麄兛赡軙?huì)做錯(cuò)甚至忘記去做。也許這個(gè)字段應(yīng)該繼續(xù)作為抽象字段,這樣類(lèi)和其它Trait 使用它的時(shí)候會(huì)被強(qiáng)制定義一個(gè)合適的值。我們會(huì)在《第6章 - Scala 高級(jí)面向?qū)ο缶幊獭分性敿?xì)討論重寫(xiě)抽象和具體成員。

另外一個(gè)解決方案是把這個(gè)字段轉(zhuǎn)移到一個(gè)單獨(dú)的類(lèi)中,這樣構(gòu)造過(guò)程可以保證用戶(hù)可以提供正確的初始化數(shù)據(jù)。這樣也許應(yīng)該說(shuō)這整個(gè)Trait 實(shí)際上應(yīng)該是一個(gè)類(lèi),這樣你才能定義一個(gè)構(gòu)造函數(shù)來(lái)初始化這個(gè)字段。

類(lèi)還是Trait?

當(dāng)我們考慮是否一個(gè)“概念”應(yīng)該成為一個(gè)Trait 或者一個(gè)類(lèi)的時(shí)候,記住作為混入的Trait 對(duì)于“附屬”行為來(lái)說(shuō)最有意義。如果你發(fā)現(xiàn)某一個(gè)Trait 經(jīng)常作為其它類(lèi)的父類(lèi)來(lái)用,導(dǎo)致子類(lèi)會(huì)有像父Trait 那樣的行為,那么考慮把它定義為一個(gè)類(lèi)吧,讓這段邏輯關(guān)系更加清晰。(我們說(shuō)像。。。的行為,而不是是。。。,因?yàn)榍罢呤抢^承更精確的定義,基于Liskov Substitution Principle -- 例如參見(jiàn) [Martin2003]。)

提示

在Trait 里避免不能用合適的默認(rèn)值初始化的具體字段。使用抽象字段,或者把這個(gè)Trait 轉(zhuǎn)換成一個(gè)有構(gòu)造函數(shù)的類(lèi)。當(dāng)然,無(wú)狀態(tài)Trait 沒(méi)有初始化的問(wèn)題。

一個(gè)實(shí)例應(yīng)該從構(gòu)造過(guò)程結(jié)束開(kāi)始,永遠(yuǎn)都在一個(gè)已知的有效的狀態(tài)下,這是優(yōu)秀的面向?qū)ο笤O(shè)計(jì)的基本原則。

概括,及下章預(yù)告

在這一章,我們學(xué)習(xí)了如何使用Trait 來(lái)封裝和共享類(lèi)之間正交的關(guān)注點(diǎn)。我們也學(xué)習(xí)了何時(shí),以及如何使用Trait,如果“堆疊”多個(gè)Trait,以及初始化Trait 的成員的規(guī)則。

在下一章,我們會(huì)探索Scala 編程中的面向?qū)ο蠡A(chǔ)。即使你是一個(gè)面向?qū)ο缶幊痰睦鲜郑阋矔?huì)希望通過(guò)閱讀下面的章節(jié)來(lái)理解Scala 面向?qū)ο蠓椒ǖ姆椒矫婷妗?/p>

51CTO推薦專(zhuān)題

[[16158]]

【編輯推薦】

  1. 《Scala編程指南》第一章
  2. 《Scala編程指南》第二章 
  3. 《Scala編程指南》第三章 
  4. 51CTO專(zhuān)訪Scala創(chuàng)始人:Scala拒絕學(xué)術(shù)化
  5. “Scala” 一個(gè)有趣的語(yǔ)言
責(zé)任編輯:佚名 來(lái)源: 51CTO
相關(guān)推薦

2010-09-14 15:34:41

Scala

2010-11-17 11:31:22

Scala基礎(chǔ)面向?qū)ο?/a>Scala

2010-09-14 13:22:17

Scala編程指南Scala

2010-09-14 14:28:58

Scala

2009-07-15 10:14:25

Scala并發(fā)性

2018-09-26 11:12:35

iOS蘋(píng)果功能

2009-09-09 14:09:35

Scala Trait

2009-09-24 09:41:00

Scala講座Scala

2010-03-11 10:34:22

Scala

2011-06-28 11:06:16

Scala

2011-08-02 09:38:25

IOS 用戶(hù)設(shè)計(jì)

2009-12-11 10:45:00

Scala講座類(lèi)型系統(tǒng)功能

2009-07-09 00:25:00

ScalaSet類(lèi)Map類(lèi)

2009-07-09 00:25:00

ScalaListTuple

2016-12-30 13:43:35

異步編程RxJava

2011-07-03 10:16:45

Core Animat

2011-12-12 11:16:02

iOS并發(fā)編程

2017-01-12 14:55:50

JavaScript編程

2010-07-20 13:32:25

Perl編程格式

2009-08-27 12:00:40

ibmdwJava
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 日本手机在线 | 亚洲一区二区三区在线 | 久草新在线 | 天天综合久久网 | 一区二区三区回区在观看免费视频 | 国产精品免费观看 | 91精品国产91久久综合桃花 | 欧美一区二区三区久久精品视 | 日韩视频在线一区二区 | 神马久久久久久久久久 | 国产欧美视频一区 | 99热精品国产 | 嫩草视频在线免费观看 | 国产精品免费一区二区三区四区 | 美女视频h | 视频精品一区 | 免费观看一级特黄欧美大片 | 欧美精品久久 | 天天看天天爽 | 日韩在线免费 | 国产日韩精品久久 | 黄色在线播放视频 | 亚洲欧美日韩高清 | 国产成人av一区二区三区 | 欧美日韩国产一区二区三区 | 欧美一区免费 | 日韩一区二区三区在线观看 | 在线观看免费av网站 | 啪啪网页 | 99久久视频 | 久久这里只有 | 亚洲精品久久久久国产 | 国产色 | 国产一级黄色网 | 国产精品亚洲综合 | 亚洲精品电影 | 在线看黄免费 | 国产乱人伦精品一区二区 | 国产精品不卡视频 | 亚洲成人自拍网 | 国产欧美日韩一区二区三区在线 |