海水正藍(lán)

          面朝大海,春暖花開(kāi)
          posts - 145, comments - 29, trackbacks - 0, articles - 1
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
          簡(jiǎn)介: Gang of Four 的解釋器設(shè)計(jì)模式 (Interpreter design pattern) 鼓勵(lì)在一個(gè)語(yǔ)言的基礎(chǔ)上構(gòu)建一個(gè)新的語(yǔ)言來(lái)實(shí)現(xiàn)擴(kuò)展。大多數(shù)函數(shù)式語(yǔ)言都能夠讓您以多種方式(如操作符重載和模式匹配)對(duì)語(yǔ)言進(jìn)行擴(kuò)展。盡管 Java™ 不支持這些技術(shù),下一代 JVM 語(yǔ)言均支持這些技術(shù),但其具體實(shí)現(xiàn)細(xì)則有所不同。在本文中,Neal Ford 將探討 Groovy、Scala 和 Clojure 如何通過(guò)以 Java 無(wú)法支持的方式來(lái)實(shí)現(xiàn)函數(shù)式擴(kuò)展,從而實(shí)現(xiàn)解釋器設(shè)計(jì)模式的目的。

          在本期 函數(shù)式思維 的文章中,我將繼續(xù)研究 Gang of Four (GoF) 設(shè)計(jì)模式(參閱 參考資料)的函數(shù)式替代解決方案。在本文中,我將研究最少人了解,但卻是最強(qiáng)大的模式之一:解釋器 (Interpreter)

          解釋器的定義是:

          給定一個(gè)語(yǔ)言,定義其語(yǔ)法表示,以及一個(gè)使用該表示來(lái)解釋語(yǔ)言中的句子的解釋器。

          換句話說(shuō),如果您正在使用的語(yǔ)言不適用于解決問(wèn)題,那么用它來(lái)構(gòu)建一個(gè)適用的語(yǔ)言。關(guān)于該方法的一個(gè)很好的示例出現(xiàn)在 Web 框架中,如 Grails 和 Ruby on Rails(參閱 參考資料),它們擴(kuò)展了自己的基礎(chǔ)語(yǔ)言(分別是 Groovy 和 Ruby),使編寫(xiě) Web 應(yīng)用程序變得更容易。

          這種模式最少人了解,因?yàn)闃?gòu)建一種新的語(yǔ)言并不常見(jiàn),需要專業(yè)的技能和慣用語(yǔ)法。它是最強(qiáng)大的 設(shè)計(jì)模式,因?yàn)樗膭?lì)您針對(duì)正在解決的問(wèn)題擴(kuò)展自己的編程語(yǔ)言。這在 Lisp(因此 Clojure 也同樣)世界是一個(gè)普遍的特質(zhì),但在主流語(yǔ)言中不太常見(jiàn)。

          當(dāng)使用禁止對(duì)語(yǔ)言本身進(jìn)行擴(kuò)展的語(yǔ)言(如 Java)時(shí),開(kāi)發(fā)人員往往將自己的思維塑造成該語(yǔ)言的語(yǔ)法;這是您的惟一選擇。然而,當(dāng)您漸漸習(xí)慣使用允許輕松擴(kuò)展的語(yǔ)言時(shí),您就會(huì)開(kāi)始將語(yǔ)言折向解決問(wèn)題的方向,而不是其他折衷的方式。

          Java 缺乏直觀的語(yǔ)言擴(kuò)展機(jī)制,除非您求助于面向方面的編程。然而,下一代的 JVM 語(yǔ)言(Groovy、Scala 和 Clojure)(參閱 參考資料)均支持以多種方式進(jìn)行擴(kuò)展。通過(guò)這樣做,它們可以達(dá)到解釋器設(shè)計(jì)模式的目的。首先,我將展示如何使用這三種語(yǔ)言實(shí)現(xiàn)操作符重載,然后演示 Groovy 和 Scala 如何讓您擴(kuò)展現(xiàn)有的類。

          操作符重載(operator overloading)

          操作符重載 是函數(shù)式語(yǔ)言的一個(gè)常見(jiàn)特性,能夠重定義操作符(如 +-*)配合新的類型工作,并表現(xiàn)出新的行為。操作符重載的缺失是 Java 形成時(shí)期的一個(gè)有意識(shí)的決定,但現(xiàn)在幾乎每一個(gè)現(xiàn)代語(yǔ)言都具備這個(gè)特性,包括在 JVM 上 Java 的天然接班人。

          Groovy

          Groovy 嘗試更新 Java 的語(yǔ)法,使其跟上潮流,同時(shí)保留其自然語(yǔ)義。因此,Groovy 通過(guò)將操作符自動(dòng)映射到方法名稱實(shí)現(xiàn)操作符重載。例如,如果您想重載 Integer+ 操作符,那么您要重寫(xiě) Integer 類的 plus() 方法。完整的映射列表已在線提供(參閱 參考資料);表 1 顯示了列表的一部分:


          表 1. Groovy 的操作符/方法映射列表的一部分
          操作符方法
          x + yx.plus(y)
          x * yx.multiply(y)
          x / yx.div(y)
          x ** yx.power(y)

          作為一個(gè)操作符重載的示例,我將在 Groovy 和 Scala 中都創(chuàng)建一個(gè) ComplexNumber 類。復(fù)數(shù) 是一個(gè)數(shù)學(xué)概念,由一個(gè)實(shí)數(shù)虛數(shù) 部分組成,一般寫(xiě)法是,例如 3 + 4i。復(fù)數(shù)在許多科學(xué)領(lǐng)域中都很常用,包括工程學(xué)、物理學(xué)、電磁學(xué)和混沌理論。開(kāi)發(fā)人員在編寫(xiě)這些領(lǐng)域的應(yīng)用程序時(shí),大大受益于能夠創(chuàng)建反映其問(wèn)題域的操作符。(有關(guān)復(fù)數(shù)的更多信息,請(qǐng)參閱 參考資料。)

          清單 1 中顯示了一個(gè) Groovy ComplexNumber 類:


          清單 1. Groovy 中的 ComplexNumber
          				 package complexnums  class ComplexNumber {    def real, imaginary    public ComplexNumber(real, imaginary) {     this.real = real     this.imaginary = imaginary   }    def plus(rhs) {     new ComplexNumber(this.real + rhs.real, this.imaginary + rhs.imaginary)   }      def multiply(rhs) {     new ComplexNumber(         real * rhs.real - imaginary * rhs.imaginary,         real * rhs.imaginary + imaginary * rhs.real)   }    String toString() {     real.toString() + ((imaginary < 0 ? "" : "+") + imaginary + "i").toString()   } } 

          清單 1 中,我創(chuàng)建一個(gè)類,保存實(shí)數(shù)和虛數(shù)部分,并且我創(chuàng)建重載的 plus()multiply() 操作符。兩個(gè)復(fù)數(shù)的相加是非常直觀的:plus() 操作符將兩個(gè)數(shù)各自的實(shí)數(shù)和虛數(shù)分別進(jìn)行相加,并產(chǎn)生結(jié)果。兩個(gè)復(fù)數(shù)的相乘需要以下公式:

          (x + yi)(u + vi) = (xu - yv) + (xv + yu)i 			

          清單 1 中的 multiply() 操作符復(fù)制該公式。它將兩個(gè)數(shù)字的實(shí)數(shù)部分相乘,然后減去虛數(shù)部分相乘的積,再加上實(shí)數(shù)和虛數(shù)分別彼此相乘的積。

          清單 2 測(cè)試復(fù)數(shù)運(yùn)算符:


          清單 2. 測(cè)試復(fù)數(shù)運(yùn)算符
          				 package complexnums  import org.junit.Test import static org.junit.Assert.assertTrue import org.junit.Before  class ComplexNumberTest {   def x, y    @Before void setup() {     x = new ComplexNumber(3, 2)     y = new ComplexNumber(1, 4)   }    @Test void plus_test() {     def z = x + y;     assertTrue 3 + 1 == z.real     assertTrue 2 + 4 == z.imaginary   }      @Test void multiply_test() {     def z = x * y     assertTrue(-5  == z.real)     assertTrue 14 == z.imaginary   } }   

          清單 2 中,plus_test()multiply_test() 方法對(duì)重載操作符的使用(兩者都以該領(lǐng)域?qū)<沂褂玫南嗤?hào)代表)與類似的內(nèi)置類型用法沒(méi)什么區(qū)別。

          Scala(和 Clojure)

          Scala 通過(guò)放棄操作符和方法之間的區(qū)別來(lái)實(shí)現(xiàn)操作符重載:操作符僅僅是具有特殊名稱的方法。因此,要使用 Scala 重寫(xiě)乘法運(yùn)算,您要重寫(xiě) * 方法。在清單 3 中,我用 Scala 創(chuàng)建復(fù)數(shù)。


          清單 3. Scala 中的復(fù)數(shù)
          				 class ComplexNumber(val real:Int, val imaginary:Int) {     def +(operand:ComplexNumber):ComplexNumber = {         new ComplexNumber(real + operand.real, imaginary + operand.imaginary)     }       def *(operand:ComplexNumber):ComplexNumber = {         new ComplexNumber(real * operand.real - imaginary * operand.imaginary,             real * operand.imaginary + imaginary * operand.real)     }      override def toString() = {         real + (if (imaginary < 0) "" else "+") + imaginary + "i"     } }   

          清單 3 中的類包括熟悉的 realimaginary 成員,以及 +* 操作符/方法。如清單 4 所示,我可以自然地使用 ComplexNumber


          清單 4. 在 Scala 中使用復(fù)數(shù)
          				 val c1 = new ComplexNumber(3, 2) val c2 = new ComplexNumber(1, 4) val c3 = c1 + c2 assert(c3.real == 4) assert(c3.imaginary == 6)  val res = c1 + c2 * c3   printf("(%s) + (%s) * (%s) = %s\n", c1, c2, c3, res) assert(res.real == -17) assert(res.imaginary == 24) 

          通過(guò)統(tǒng)一操作符和方法,Scala 使操作符重載變成一件小事。Clojure 使用相同的機(jī)制來(lái)重載操作符。例如,以下 Clojure 代碼定義了一個(gè)重載的 ** 操作符:

          (defn ** [x y] (Math/pow x y)) 

          回頁(yè)首

          擴(kuò)展類

          類似于操作符重載,下一代的 JVM 語(yǔ)言允許您擴(kuò)展類(包括核心 Java 類),擴(kuò)展的方式在 Java 語(yǔ)言本身是不可能實(shí)現(xiàn)的。這些設(shè)施通常用于構(gòu)建領(lǐng)域特定的語(yǔ)言 (DSL)。雖然 GOF 從來(lái)沒(méi)有考慮過(guò) DSL(因?yàn)樗鼈兣c當(dāng)時(shí)流行的語(yǔ)言沒(méi)有共同點(diǎn)),DSL 卻體現(xiàn)了解釋器設(shè)計(jì)模式的初衷。

          通過(guò)將計(jì)量單位和其他修飾符添加給 Integer 等核心類,您可以(就像添加操作符一樣)更緊密地對(duì)現(xiàn)實(shí)問(wèn)題進(jìn)行建模。Groovy 和 Scala 都支持這樣做,但它們使用不同的機(jī)制。

          Groovy 的 Expando 和類別類

          Groovy 包括兩種對(duì)現(xiàn)有類添加方法的機(jī)制:ExpandoMetaClass類別。(在 函數(shù)式思維:函數(shù)設(shè)計(jì)模式,第 2 部分 中,我在適配器模式的上下文中詳細(xì)介紹過(guò) ExpandoMetaClass。)

          比方說(shuō),您的公司由于離奇的遺留原因,需要以浪(furlongs,英國(guó)的計(jì)量單位)/每?jī)芍芏皇且杂⒗铮啃r(shí) (MPH) 的方法來(lái)表達(dá)速度,開(kāi)發(fā)人員發(fā)現(xiàn)自己經(jīng)常要執(zhí)行這種轉(zhuǎn)換。使用 Groovy 的  ExpandoMetaClass,您可以添加一個(gè) FF 屬性給處理轉(zhuǎn)換的 Integer ,如清單 5 所示:


          清單 5. 使用 ExpandoMetaClass 添加一個(gè)浪/兩周的計(jì)量單位給 Integer
          				 static {   Integer.metaClass.getFF { ->     delegate * 2688   } }  @Test void test_conversion_with_expando() {   assertTrue 1.FF == 2688 } 

          ExpandoMetaClass 的替代方法是,創(chuàng)建一個(gè)類別 包裝器類,這是從 Objective-C 借來(lái)的概念。在清單 6 中,我添加了一個(gè)(小寫(xiě)) ff 屬性給 Integer


          清單 6. 通過(guò)一個(gè)類別類添加計(jì)量單位
          				 class FFCategory {   static Integer getFf(Integer self) {     self * 2688   } }  @Test void test_conversion_with_category() {   use(FFCategory) {     assertTrue 1.ff == 2688   } } 

          一個(gè)類別類是一個(gè)帶有一組靜態(tài)方法集合的普通類。每個(gè)方法接受至少一個(gè)參數(shù);第一個(gè)參數(shù)是這種方法增強(qiáng)的類型。例如,在 清單 6 中, FFCategory 類擁有一個(gè) getFf() 方法,它接受一個(gè) Integer 參數(shù)。當(dāng)這個(gè)類別類與 use 關(guān)鍵字一起使用時(shí),代碼塊內(nèi)所有相應(yīng)類型都被增強(qiáng)。在單元測(cè)試中,我可以在代碼塊內(nèi)引用 ff 屬性(記住,Groovy 自動(dòng)將 get 方法轉(zhuǎn)換為屬性引用),如在 清單 6 的底部所示。

          有兩種機(jī)制可供選擇,讓您可以更準(zhǔn)確地控制增強(qiáng)的范圍。例如,如果整個(gè)系統(tǒng)使用 MPH 作為速度的默認(rèn)單位,但也需要頻繁轉(zhuǎn)換為浪/每?jī)芍埽敲词褂? ExpandoMetaClass 進(jìn)行全局修改將是適當(dāng)?shù)摹?

          您可能對(duì)重新開(kāi)放核心 JVM 類的有效性持懷疑態(tài)度,擔(dān)心會(huì)產(chǎn)生廣泛深遠(yuǎn)的影響。類別類讓您限制潛在危險(xiǎn)性增強(qiáng)的范圍。以下是一個(gè)來(lái)自真實(shí)世界的開(kāi)源項(xiàng)目示例,它極好地利用了這一機(jī)制。

          easyb 項(xiàng)目(參閱 參考資料)讓您可以編寫(xiě)測(cè)試,以驗(yàn)證正接受測(cè)試的類的各個(gè)方面。請(qǐng)研究清單 7 所示的 easyb 測(cè)試代碼片段:


          清單 7. easyb 測(cè)試一個(gè) queue
          				 it "should dequeue items in same order enqueued", {     [1..5].each {val ->         queue.enqueue(val)     }     [1..5].each {val ->         queue.dequeue().shouldBe(val)     } } 

          queue 類不包括 shouldBe() 方法,這是我在測(cè)試的驗(yàn)證階段所調(diào)用的方法。easyb 框架已為我添加了該方法;清單 8 中所顯示的在 easyb 源代碼中的 it() 方法定義,演示了該過(guò)程:


          清單 8. easyb 的 it() 方法定義
          				 def it(spec, closure) {   stepStack.startStep(BehaviorStepType.IT, spec)   closure.delegate = new EnsuringDelegate()   try {     if (beforeIt != null) {       beforeIt()     }     listener.gotResult(new Result(Result.SUCCEEDED))     use(categories) {       closure()     }     if (afterIt != null) {       afterIt()     }   } catch (Throwable ex) {     listener.gotResult(new Result(ex))   } finally {     stepStack.stopStep()   } }  class BehaviorCategory {   // ...    static void shouldBe(Object self, value) {     shouldBe(self, value, null)   }    //... } 

          清單 8中,it() 方法接受了一個(gè) spec (描述測(cè)試的一個(gè)字符串)和一個(gè)代表測(cè)試的主體的閉包塊。在方法的中間,閉包會(huì)在 BehaviorCategory 塊內(nèi)執(zhí)行,該塊出現(xiàn)在清單的底部。BehaviorCategory 增強(qiáng) Object,允許 Java 世界中的任何 實(shí)例驗(yàn)證其值。

          通過(guò)允許選擇性增強(qiáng)駐留在層次結(jié)構(gòu)頂層的 Object,Groovy 的開(kāi)放類機(jī)制可以輕松地實(shí)現(xiàn)為任何實(shí)例驗(yàn)證結(jié)果,但它限制了對(duì) use 塊主體的修改。

          Scala 的隱式轉(zhuǎn)換

          Scala 使用隱式轉(zhuǎn)換 來(lái)模擬現(xiàn)有類的增強(qiáng)。隱式轉(zhuǎn)換不會(huì)對(duì)類添加方法,但允許語(yǔ)言自動(dòng)將一個(gè)對(duì)象轉(zhuǎn)換成擁有所需方法的相應(yīng)類型。例如,我不能將 isBlank() 方法添加到 String 類中,但我可以創(chuàng)建一個(gè)隱式轉(zhuǎn)換,將 String 自動(dòng)轉(zhuǎn)換為擁有這種方法的類。

          作為一個(gè)示例,我想將 append() 方法添加到 Array,這讓我可以輕松地將 Person 實(shí)例添加到適當(dāng)類型的數(shù)組,如清單 9 所示:


          清單 9.將一個(gè)方法添加到 Array 中,以增加人員
          				 case class Person (firstName: String, lastName: String) {}  class PersonWrapper(a: Array[Person]) {   def append(other: Person) = {     a ++ Array(other)   }   def +(other: Person) = {     a ++ Array(other)   } }      implicit def listWrapper(a: Array[Person]) = new PersonWrapper(a) 

          清單 9中,我創(chuàng)建一個(gè)簡(jiǎn)單的 Person 類,它帶有若干個(gè)屬性。為了使 Array[Person](在 Scala 中,一般使用 [ ] 而不是 < > 作為分隔符)Person 可知,我創(chuàng)建一個(gè) PersonWrapper 類,它包括所需的 append() 方法。在清單的底部,我創(chuàng)建一個(gè)隱式轉(zhuǎn)換,當(dāng)我在數(shù)組上調(diào)用 append() 方法時(shí),隱式轉(zhuǎn)換會(huì)自動(dòng)將一個(gè) Array[Person] 轉(zhuǎn)換為 PersonWrapper。清單 10 測(cè)試該轉(zhuǎn)換:


          清單 10. 測(cè)試對(duì)現(xiàn)有類的自然擴(kuò)展
          				 val p1 = new Person("John", "Doe") var people = Array[Person]() people = people.append(p1) 

          清單 9中,我也為 PersonWrapper 類添加了一個(gè) + 方法。清單 11 顯示了我如何使用操作符的這個(gè)漂亮直觀的版本:


          清單 11. 修改語(yǔ)言以增強(qiáng)可讀性
          				 people = people + new Person("Fred", "Smith") for (p <- people)   printf("%s, %s\n", p.lastName, p.firstName) 

          Scala 實(shí)際上并未對(duì)原始的類添加一個(gè)方法,但它通過(guò)自動(dòng)轉(zhuǎn)換成一個(gè)合適的類型,提供了這樣做的外觀。使用 Groovy 等語(yǔ)言進(jìn)行元編程所需要的相同工作在 Scala 中也需要,以避免過(guò)多使用隱式轉(zhuǎn)換而產(chǎn)生由相互關(guān)聯(lián)的類所組成的令人費(fèi)解的網(wǎng)。但是,在正確使用時(shí),隱式轉(zhuǎn)換可以幫助您編寫(xiě)表達(dá)非常清晰的代碼。

          回頁(yè)首

          結(jié)束語(yǔ)

          來(lái)自 GoF 的原始解釋器設(shè)計(jì)模式建議創(chuàng)建一個(gè)新語(yǔ)言,但其基礎(chǔ)語(yǔ)言并不支持我們今天所掌握的良好擴(kuò)展機(jī)制。下一代 Java 語(yǔ)言都通過(guò)使用多種技術(shù)來(lái)支持語(yǔ)言級(jí)別的可擴(kuò)展性。在本期文章中,我演示了操作符重載如何在 Groovy、Scala 和 Clojure 中工作,并研究了在 Groovy 和 Scala 中的類擴(kuò)展。

          在下期文章中,我將展示 Scala 風(fēng)格的模式匹配和泛型的組合如何取代一些傳統(tǒng)的設(shè)計(jì)模式。該討論的中心是一個(gè)在函數(shù)式錯(cuò)誤處理中也起著作用的概念,這一概念將是我們下期文章的主題。


          參考資料

          學(xué)習(xí)

          獲得產(chǎn)品和技術(shù)

          討論

          關(guān)于作者

          Neal Ford

          Neal Ford 是一家全球性 IT 咨詢公司 ThoughtWorks 的軟件架構(gòu)師和 Meme Wrangler。他的工作還包括設(shè)計(jì)和開(kāi)發(fā)應(yīng)用程序、教材、雜志文章、課件和視頻/DVD 演示,而且他是各種技術(shù)書(shū)籍的作者或編輯,包括最近的新書(shū) The Productive Programmer 。他主要的工作重心是設(shè)計(jì)和構(gòu)建大型企業(yè)應(yīng)用程序。他還是全球開(kāi)發(fā)人員會(huì)議上的國(guó)際知名演說(shuō)家。請(qǐng)?jiān)L問(wèn)他的 Web 站點(diǎn)


          http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp?view_by=search&sort_by=Date&sort_order=desc&view_by=Search&search_by=%E5%87%BD%E6%95%B0%E5%BC%8F%E6%80%9D%E7%BB%B4&dwsearch.x=18&dwsearch.y=11


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 灌南县| 波密县| 苍梧县| 石狮市| 儋州市| 会理县| 朝阳县| 林口县| 班玛县| 泗水县| 桦南县| 西安市| 耒阳市| 疏勒县| 沛县| 台中市| 神农架林区| 台江县| 黄浦区| 疏勒县| 定边县| 务川| 巴林左旗| 井冈山市| 广河县| 霍城县| 错那县| 聂荣县| 科尔| 和静县| 德江县| 青岛市| 福海县| 招远市| 博客| 枞阳县| 会宁县| 荆州市| 元朗区| 正阳县| 崇文区|