鍍金池/ 教程/ Scala/ Trait 用來實(shí)現(xiàn)可疊加的修改操作
包對(duì)象
Ordered Trait
組合和繼承–定義 final 成員
基本數(shù)據(jù)類型
Match 表達(dá)式
類和對(duì)象 (三)
操作基本數(shù)據(jù)類型
for 表達(dá)式
組合和繼承–重載成員函數(shù)和方法
類和對(duì)象 (二)
組合和繼承–定義 factory 對(duì)象
組合和繼承–多態(tài)和動(dòng)態(tài)綁定
Trait 的基本概念
if 表達(dá)式
組合和繼承–抽象類
函數(shù)–函數(shù)字面量的一些簡化寫法
while 循環(huán)
組合和繼承–使用組合還是繼承?
訪問控制修飾符
Trait 示例–Rectangular 對(duì)象
組合和繼承–定義參數(shù)化成員變量
組合和繼承–定義無參數(shù)方法
類和對(duì)象 (一)
函數(shù)–閉包
函數(shù)–類成員函數(shù)
Scala 基本數(shù)據(jù)類型的實(shí)現(xiàn)方法
try 表達(dá)式處理異常
選擇瘦接口還是胖接口設(shè)計(jì)?
組合和繼承–小結(jié)
創(chuàng)建新的控制結(jié)構(gòu)
使用 import
為訪問控制修飾符添加作用域
Scala 的類層次關(guān)系
類和對(duì)象 (五)
傳名參數(shù)
柯里化函數(shù)
函數(shù)–頭等公民
組合和組合和繼承–定義 heighten 和 widen 函數(shù)
使用 Package–將代碼放入包中
隱含的 import
所有類的公共子類–底層類型
進(jìn)一步 Scala
函數(shù)–局部函數(shù)
引用包中的代碼
組合和繼承–使用 override 修飾符
組合和繼承–實(shí)現(xiàn)類 Element 的 above,beside 和 toString()方法
類和對(duì)象 (四)
函數(shù)–尾遞歸
沒有“break”和“continue”的日子
組合和繼承–調(diào)用基類構(gòu)造函數(shù)
減低代碼重復(fù)
函數(shù)–函數(shù)–可變參數(shù),命名參數(shù),缺省參數(shù)
起步 Scala
組合和繼承–擴(kuò)展類
函數(shù)–部分應(yīng)用的函數(shù)
開始神奇的 Scala編程之旅
組合和繼承–概述
Trait 用來實(shí)現(xiàn)可疊加的修改操作

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

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

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

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

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

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

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

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

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

有了這三個(gè) 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)成了一個(gè)新的隊(duì)列類型,每次添加的都是參數(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),簡單的說,越后混合的 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ì)列為空(過濾掉了負(fù)數(shù)),我們沒有添加錯(cuò)誤處理,元素 -4 沒有被添加到了隊(duì)列中。

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