我們已經(jīng)看到 Trait 的一個(gè)主要用法,將一個(gè)瘦接口變成胖接口,本篇我們介紹 Trait 的另外一個(gè)重要用法,為類(lèi)添加一些可以疊加的修改操作。Trait 能夠修改類(lèi)的方法,并且能夠通過(guò)疊加這些操作(不同組合)修改類(lèi)的方法。
我們來(lái)看這樣一個(gè)例子,修改一個(gè)整數(shù)隊(duì)列,這個(gè)隊(duì)列有兩個(gè)方法:put 為隊(duì)列添加一個(gè)元素,get 從隊(duì)列讀取一個(gè)元素。隊(duì)列是先進(jìn)先出,因此 get 讀取的順序和 put 的順序是一致的。
對(duì)于上面的隊(duì)列,我們定義如下三個(gè) Trait 類(lèi)型:
這三個(gè) Trait 代表了修改操作,因?yàn)樗鼈兛梢杂脕?lái)修改隊(duì)列類(lèi)對(duì)象,而不是為隊(duì)列類(lèi)定義所有可能的操作。這三個(gè)操作是可以疊加的,也就是說(shuō),你可以通過(guò)這三個(gè)基本操作的任意不同組合和原始的隊(duì)列類(lèi)“混合”,從而可以得到你所需要的新的隊(duì)列類(lèi)的修改操作。
為了實(shí)現(xiàn)這個(gè)整數(shù)隊(duì)列,我們可以定義這個(gè)整數(shù)隊(duì)列的一個(gè)基本實(shí)現(xiàn)如下:
import scala.collection.mutable.ArrayBuffer
abstract class IntQueue {
def get():Int
def put(x:Int)
}
class BasicIntQueue extends IntQueue{
private val buf =new ArrayBuffer[Int]
def get()= buf.remove(0)
def put(x:Int) { buf += x }
}
下面我們可以使用這個(gè)實(shí)現(xiàn),來(lái)完成隊(duì)列的一些基本操作:
scala> val queue = new BasicIntQueue
queue: BasicIntQueue = BasicIntQueue@60d134d3
scala> queue.put (10)
scala> queue.put(20)
scala> queue.get()
res2: Int = 10
scala> queue.get()
res3: Int = 20
這個(gè)實(shí)現(xiàn)完成了對(duì)象的基本操作,看起來(lái)了還可以,但如果此時(shí)有新的需求,希望在添加元素時(shí),添加元素的雙倍,并且過(guò)濾掉負(fù)數(shù),你可以直接修改 put 方法 來(lái)完成,但之后需求又變了,添加元素時(shí),添加的為參數(shù)的遞增值,你也可以修改 put 方法,這樣顯得隊(duì)列的實(shí)現(xiàn)不夠靈活。
我們來(lái)看看如果使用 Trait 會(huì)有什么結(jié)果,我們實(shí)現(xiàn) Doubling,Incrementing,F(xiàn)iltering 如下:
trait Doubling extends IntQueue{
abstract override def put(x:Int) { super.put(2*x)}
}
trait Incrementing extends IntQueue{
abstract override def put(x:Int) { super.put(x+1)}
}
trait Filtering extends IntQueue{
abstract override def put (x:Int){
if(x>=0) super.put(x)
}
}
我們可以看到所有的 Trait 實(shí)現(xiàn)都已 IntQueue 為基類(lèi),這保證這些 Trait 只能和同樣繼承了 IntQueue 的類(lèi)“混合”,比如和 BasicIntQueue 混合,而不可以和比如前面定義的 Rational 類(lèi)混合。
此外 Trait 的 put 方法中使用了 super,通常情況下對(duì)于普通的類(lèi)這種調(diào)用是不合法的,但對(duì)于 trait來(lái)說(shuō),這種方法是可行的,這是因?yàn)?trait 中的 super 調(diào)用是動(dòng)態(tài)綁定的,只要和這個(gè) Trait 混合在其他類(lèi)或 Trait 之后,而這個(gè)其它類(lèi)或 Trait 定義了 super 調(diào)用的方法即可。這種方法是實(shí)現(xiàn)可以疊加的修改操作是必須的,并且注意使用 abstract override 修飾符,這種使用方法僅限于 Trait 而不能用作 Class 的定義上。
有了這三個(gè) Trait 的定義,我們可以非常靈活的組合這些 Trait 來(lái)修改 BasicIntQueue 的操作。
首先我們使用 Doubling Trait
scala> val queue = new BasicIntQueue with Doubling
queue: BasicIntQueue with Doubling = $anon$1@3b004676
scala> queue.put(10)
scala> queue.get()
res1: Int = 20
這里通過(guò) BasicIntQueue 和 Doubling 混合,我們構(gòu)成了一個(gè)新的隊(duì)列類(lèi)型,每次添加的都是參數(shù)的倍增。
我們?cè)谑褂?BasicIntQueue 同時(shí)和 Doubling 和 Increment 混合,注意我們構(gòu)造兩個(gè)不同的整數(shù)隊(duì)列,不同時(shí) Doubling 和 Increment 的混合的順序
scala> val queue1 = new BasicIntQueue with Doubling with Incrementing
queue1: BasicIntQueue with Doubling with Incrementing = $anon$1@35849932
scala> val queue2 = new BasicIntQueue with Incrementing with Doubling
queue2: BasicIntQueue with Incrementing with Doubling = $anon$1@4a4cdea2
scala> queue1.put(10)
scala> queue1.get()
res4: Int = 22
scala> queue2.put(10)
scala> queue2.get()
res6: Int = 21
可以看到結(jié)果和 Trait 混合的順序有關(guān),簡(jiǎn)單的說(shuō),越后混合的 Trait 作用越大。因此 queue1 先+1,然后 X2,而 queue 先 X2 后+1。
最后我們看看三個(gè) Trait 混合的一個(gè)例子:
scala> val queue = new BasicIntQueue with Doubling with Incrementing with Filtering
queue: BasicIntQueue with Doubling with Incrementing with Filtering = $anon$1@73a4eb2d
scala> queue.put(10)
scala> queue.put(-4)
scala> queue.put(20)
scala> queue.get()
res10: Int = 22
scala> queue.get()
res11: Int = 42
scala> queue.get()
java.lang.IndexOutOfBoundsException: 0
at scala.collection.mutable.ResizableArray$class.apply(ResizableArray.scala:44)
at scala.collection.mutable.ArrayBuffer.apply(ArrayBuffer.scala:44)
at scala.collection.mutable.ArrayBuffer.remove(ArrayBuffer.scala:163)
at BasicIntQueue.get(<console>:11)
at .<init>(<console>:15)
at .<clinit>(<console>)
at .<init>(<console>:11)
at .<clinit>(<console>)
at $print(<console>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
at java.lang.Thread.run(Thread.java:744)
最后的異常時(shí)因?yàn)殛?duì)列為空(過(guò)濾掉了負(fù)數(shù)),我們沒(méi)有添加錯(cuò)誤處理,元素 -4 沒(méi)有被添加到了隊(duì)列中。
由此可以看出,通過(guò) Trait 可以提高類(lèi)的實(shí)現(xiàn)的靈活性,你可以通過(guò)這些 Trait 的不同組合定義了多種不同的對(duì)列類(lèi)型。
更多建議: