Scala Trait 用來實(shí)現(xiàn)可疊加的修改操作

2018-09-28 18:18 更新

Trait 用來實(shí)現(xiàn)可疊加的修改操作

我們已經(jīng)看到 Trait 的一個主要用法,將一個瘦接口變成胖接口,本篇我們介紹 Trait 的另外一個重要用法,為類添加一些可以疊加的修改操作。Trait 能夠修改類的方法,并且能夠通過疊加這些操作(不同組合)修改類的方法。

我們來看這樣一個例子,修改一個整數(shù)隊(duì)列,這個隊(duì)列有兩個方法:put 為隊(duì)列添加一個元素,get 從隊(duì)列讀取一個元素。隊(duì)列是先進(jìn)先出,因此 get 讀取的順序和 put 的順序是一致的。

對于上面的隊(duì)列,我們定義如下三個 Trait 類型:

  • Doubling : 隊(duì)列中所有元素X2。
  • Incrementing: 隊(duì)列中所有元素遞增。
  • Filtering: 過濾到隊(duì)列中所有負(fù)數(shù)。

這三個 Trait 代表了修改操作,因?yàn)樗鼈兛梢杂脕硇薷年?duì)列類對象,而不是為隊(duì)列類定義所有可能的操作。這三個操作是可以疊加的,也就是說,你可以通過這三個基本操作的任意不同組合和原始的隊(duì)列類“混合”,從而可以得到你所需要的新的隊(duì)列類的修改操作。

為了實(shí)現(xiàn)這個整數(shù)隊(duì)列,我們可以定義這個整數(shù)隊(duì)列的一個基本實(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 }
}

下面我們可以使用這個實(shí)現(xiàn),來完成隊(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

這個實(shí)現(xiàn)完成了對象的基本操作,看起來了還可以,但如果此時有新的需求,希望在添加元素時,添加元素的雙倍,并且過濾掉負(fù)數(shù),你可以直接修改 put 方法 來完成,但之后需求又變了,添加元素時,添加的為參數(shù)的遞增值,你也可以修改 put 方法,這樣顯得隊(duì)列的實(shí)現(xiàn)不夠靈活。

我們來看看如果使用 Trait 會有什么結(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 為基類,這保證這些 Trait 只能和同樣繼承了 IntQueue 的類“混合”,比如和 BasicIntQueue 混合,而不可以和比如前面定義的 Rational 類混合。

此外 Trait 的 put 方法中使用了 super,通常情況下對于普通的類這種調(diào)用是不合法的,但對于 trait來說,這種方法是可行的,這是因?yàn)?trait 中的 super 調(diào)用是動態(tài)綁定的,只要和這個 Trait 混合在其他類或 Trait 之后,而這個其它類或 Trait 定義了 super 調(diào)用的方法即可。這種方法是實(shí)現(xiàn)可以疊加的修改操作是必須的,并且注意使用 abstract override 修飾符,這種使用方法僅限于 Trait 而不能用作 Class 的定義上。

有了這三個 Trait 的定義,我們可以非常靈活的組合這些 Trait 來修改 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

這里通過 BasicIntQueue 和 Doubling 混合,我們構(gòu)成了一個新的隊(duì)列類型,每次添加的都是參數(shù)的倍增。

我們在使用 BasicIntQueue 同時和 Doubling 和 Increment 混合,注意我們構(gòu)造兩個不同的整數(shù)隊(duì)列,不同時 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),簡單的說,越后混合的 Trait 作用越大。因此 queue1 先+1,然后 X2,而 queue 先 X2 后+1。

最后我們看看三個 Trait 混合的一個例子:

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)

最后的異常時因?yàn)殛?duì)列為空(過濾掉了負(fù)數(shù)),我們沒有添加錯誤處理,元素 -4 沒有被添加到了隊(duì)列中。

由此可以看出,通過 Trait 可以提高類的實(shí)現(xiàn)的靈活性,你可以通過這些 Trait 的不同組合定義了多種不同的對列類型。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號