招生熱線
0755-86191118 0755-86191118
我的位置: 首頁 > 學(xué)習(xí)專區(qū) > JAVA技術(shù) > Java 下一代: 沒有繼承性的擴(kuò)展,第 1 部分

Java 下一代: 沒有繼承性的擴(kuò)展,第 1 部分

2014-05-07 14:49:57
來源:
[導(dǎo)讀] Java 語言的設(shè)計(jì)有目的地進(jìn)行了一定的刪減,以避免前代產(chǎn)品中已發(fā)現(xiàn)的一些問題。例如,Java 語言的設(shè)計(jì)人員感覺 C++ 中的多重繼承性帶
Java 語言的設(shè)計(jì)有目的地進(jìn)行了一定的刪減,以避免前代產(chǎn)品中已發(fā)現(xiàn)的一些問題。例如,Java 語言的設(shè)計(jì)人員感覺 C++ 中的多重繼承性帶來了太多復(fù)雜性,所以它們選擇不包含該特性。事實(shí)上,他們在該語言中很少構(gòu)建擴(kuò)展性選項(xiàng),僅依靠單一繼承和接口。

其他語言(包括 Java 下一代語言)存在巨大的擴(kuò)展?jié)摿ΑT诒酒诤徒酉聛淼膬善谖恼轮校覍⑻剿鲾U(kuò)展 Java 類而不涉及繼承性的途徑。在本文中,您會了解如何向現(xiàn)有類添加方法,無論是直接還是通過語法糖 (syntactic sugar)。

表達(dá)式問題

表達(dá)式問題是最近的計(jì)算機(jī)科學(xué)歷史上的一個眾所周知的觀察結(jié)果,首創(chuàng)于貝爾實(shí)驗(yàn)室的 Philip Wadler 的一篇未發(fā)表的論文(參見 參考資料)。(Stuart Sierra 在其 developerWorks 文章 “通過 Clojure 1.2 解決表達(dá)式問題” 中出色地解釋了它。在這篇文章中,Wadler 說道:

表達(dá)式問題是老問題的新名字。我們的目標(biāo)是通過案例定義數(shù)據(jù)類型,在這里,在不重新編譯現(xiàn)有代碼的情況下,您可以將新的案例添加到數(shù)據(jù)類型和數(shù)據(jù)類型的新函數(shù)中,同時保留靜態(tài)類型安全(例如,沒有轉(zhuǎn)換)。

換句話說,您如何向一個分層結(jié)構(gòu)中的類添加功能,而不求助于類型轉(zhuǎn)換或 if 語句?

我們將通過一個簡單的例子來表明表達(dá)式問題在真實(shí)世界中的表現(xiàn)形式。假設(shè)您公司始終假設(shè)應(yīng)用程序中的長度單位為米,沒有在您的類中為任何其他長度單位構(gòu)建任何功能。但是,有一天,您公司與一家競爭對手合并了,而這個競爭對手始終假設(shè)長度單位為英尺。

解決該問題的一種方法是,通過使用轉(zhuǎn)換方法擴(kuò)展 Integer,使兩種格式之間的切換變得無關(guān)緊要。現(xiàn)代語言提供了多種解決方案來實(shí)現(xiàn)此目的;在本期中,我將重點(diǎn)介紹其中的 3 種:

開放類包裝器類協(xié)議回頁首Groovy 的類別和 ExpandoMetaClass

Groovy 包含兩種使用開放類 擴(kuò)展現(xiàn)有的類的不同方式,“重新開放” 一個類定義來實(shí)現(xiàn)更改(例如添加、更改或刪除方法)的能力。

類別類

類別類(一種借鑒自 Objective-C 的概念)是包含靜態(tài)方法的常規(guī)類。每個方法至少接受一個參數(shù),該參數(shù)表示方法擴(kuò)充的類型。如果希望向Integer 添加方法,例如我需要接受該類型作為第一個參數(shù)的靜態(tài)方法,如清單 1 所示:

清單 1. Groovy 的類別類

class IntegerConv {

static Double getAsMeters(Integer self) {

self * 0.30480

}

static Double getAsFeet(Integer self) {

self * 3.2808

}

}

清單 1 中的 IntegerConv 類包含兩個擴(kuò)充方法,每個擴(kuò)充方法都接受一個名為 self(一個通用的慣用名稱)的 Integer 參數(shù)。要使用這些方法,我必須將引用代碼包裝在一個 use 代碼塊中,如清單 2 所示:

清單 2. 使用類別類

@Test void test_conversion_with_category() {

use(IntegerConv) {

assertEquals(1 * 3.2808, 1.asFeet, 0.1)

assertEquals(1 * 0.30480, 1.asMeters, 0.1)

}

}

清單 2 中有兩個特別有趣的地方。首先,盡管 清單 1 中的擴(kuò)展方法名為 getAsMeters(),但我將它稱為 1.asMeters。Groovy 圍繞 Java 中的屬性的語法糖使我能夠執(zhí)行 getAsMeters() 方法,好像它是名為 asMeters 的類的一個字段一樣。如果我在擴(kuò)展方法中省略了 as,對擴(kuò)展方法的調(diào)用需要使用空括號,就像 1.asMeters() 中一樣。一般而言,我喜歡更干凈的屬性語法,這是編寫特定于域的語言 (DSL) 的一種常見技巧。

清單 2 中第二個需要注意的地方是對 asFeet 和 asMeters 的調(diào)用。在 use 代碼塊中,我同等地調(diào)用新方法和內(nèi)置方法。該擴(kuò)展在 use 代碼塊的詞法范圍內(nèi)是透明的,這很好,因?yàn)樗拗屏藬U(kuò)充(有時是一些核心)類的范圍。

ExpandoMetaClass

類別是 Groovy 添加的第一種擴(kuò)展機(jī)制。但事實(shí)證明對構(gòu)建 Grails(基于 Groovy 的 Web 框架)而言,Groovy 的詞法范圍限制太多了。由于不滿類別中的限制,Grails 的創(chuàng)建者之一 Graeme Rocher 向 Groovy 添加了另一種擴(kuò)展機(jī)制:ExpandoMetaClass。

ExpandoMetaClass 是一種懶惰實(shí)例化的擴(kuò)展持有者,它可從任何類 “成長” 而來。清單 3 展示了如何使用 ExpandoMetaClass,為我的Integer 類實(shí)現(xiàn)我的擴(kuò)展:

清單 3. 使用 ExpandoMetaClass 擴(kuò)展 Integer

class IntegerConvTest{

static {

Integer.metaClass.getAsM { ->

delegate * 0.30480

}

Integer.metaClass.getAsFt { ->

delegate * 3.2808

}

}

@Test void conversion_with_expando() {

assertTrue 1.asM == 0.30480

assertTrue 1.asFt == 3.2808

}

}

在 清單 3 中,我使用 metaClass holder 添加 asM 和 asFt 屬性,采用與 清單 2 相同的命名約定。對 metaclass 的調(diào)用出現(xiàn)在測試類的一個靜態(tài)初始化器中,因?yàn)槲冶仨毚_保擴(kuò)充操作在遇到擴(kuò)展方法之前發(fā)生。

類別類和 ExpandoMetaClass 都在內(nèi)置方法之前調(diào)用擴(kuò)展類方法。這使您能夠添加、更改或刪除現(xiàn)有方法。清單 4 給出了一個示例:

清單 4. 取代現(xiàn)有方法的擴(kuò)展類

@Test void expando_order() {

try {

1.decode()

} catch(NullPointerException ex) {

println("can't decode with no parameters")

}

Integer.metaClass.decode { ->

delegate * Math.PI;

}

assertEquals(1.decode(), Math.PI, 0.1)

}

清單 4 中的第一個 decode() 方法調(diào)用是一個內(nèi)置的靜態(tài) Groovy 方法,它設(shè)計(jì)用于更改整數(shù)編碼。正常情況下,它會接受一個參數(shù);如果調(diào)用時沒有任何參數(shù),它將拋出 NullPointerException。但是,當(dāng)我使用自己的 decode() 方法擴(kuò)充 Integer 類時,它會取代原始類。

回頁首Scala 的隱式轉(zhuǎn)換

Scala 使用包裝器類 來解決表達(dá)式問題的這個方面。要向一個類添加一個方法,可將它添加到一個幫助類中,然后提供從原始類到您的幫助器的隱式轉(zhuǎn)換。在執(zhí)行轉(zhuǎn)換之后,您就可以從幫助器隱式地調(diào)用該方法,而不是從原始類調(diào)用它。清單 5 中的示例使用了這種技術(shù):

清單 5. Scala 的隱式轉(zhuǎn)換

class UnitWrapper(i: Int) {

def asFt = {

i * 3.2808

}

def asM = {

i * 0.30480

}

}

implicit def unitWrapper(i:Int) = new UnitWrapper(i)

println("1 foot = " + 1.asM + " meters");

println("1 meter = " + 1.asFt + "foot")

在 清單 5 中,我定義了一個名為 UnitWrapper 的幫助器類,它接受一個構(gòu)造函數(shù)參數(shù)和兩個方法:asFt 和 asM。在擁有轉(zhuǎn)換值的幫助類后,我創(chuàng)建了一個 implicit def,實(shí)例化一個新的 UnitWrapper。要調(diào)用該方法,可以像調(diào)用原始類的一個方法那樣調(diào)用它,比如 1.asM。當(dāng) Scala 未在 Integer 類上找到 asM 方法時,它會檢查是否存在隱式轉(zhuǎn)換,從而允許將調(diào)用類轉(zhuǎn)換為一個包含目標(biāo)方法的類。像 Groovy 一樣,Scala 擁有語法糖,因此我能夠省略方法調(diào)用的括號,但這是一種語言特性而不是命名約定。

Scala 中的轉(zhuǎn)換幫助器通常是 object 而不是類,但我使用了一個類,因?yàn)槲蚁M麄鬟f一個值作為構(gòu)造函數(shù)參數(shù)(object 不允許這么做)。

Scala 中的隱式轉(zhuǎn)換是一種擴(kuò)充現(xiàn)有類的精妙且類型安全的方式,但不能向開放類一樣,使用這種機(jī)制更改或刪除現(xiàn)有方法。

回頁首Clojure 的協(xié)議

Clojure 采用了另一種方法來解決表達(dá)式問題的這個方面,那就是結(jié)合使用 extend 函數(shù)和 Clojure 協(xié)議 抽象。協(xié)議在概念上類似于一個 Java 接口:一個沒有實(shí)現(xiàn)的方法簽名集合。盡管 Clojure 實(shí)質(zhì)上不是面向?qū)ο蟮模瞧蛴诤瘮?shù),但您可以與類進(jìn)行交互(并擴(kuò)展它們),并將方法映射到函數(shù)。

為了擴(kuò)展數(shù)字以添加轉(zhuǎn)換,我定義了一個協(xié)議,它包含我的兩個函數(shù)(asF 和 asM)。我可使用該協(xié)議 extend 一個現(xiàn)有類(比如Number)。extend 函數(shù)接受目標(biāo)類作為第一個參數(shù),接受該協(xié)議作為第二個參數(shù),以及一個使用函數(shù)名為鍵并使用實(shí)現(xiàn)(以匿名函數(shù)形式)為值的映射。清單 6 顯示了 Clojure 單位轉(zhuǎn)換:

清單 6. Clojure 的擴(kuò)展協(xié)議

(defprotocol UnitConversions

(asF [this])

(asM [this]))

(extend Number

UnitConversions

{:asF (fn [this] (* this 3.2808))

:asM #(* % 0.30480)})

我可以在 Clojure REPL(interactive read-eval-print loop,交互式讀取-重新運(yùn)算-打印循環(huán))上使用新的擴(kuò)展來驗(yàn)證該轉(zhuǎn)換:

user=> (println "1 foot is " (asM 1) " meters")

1 foot is 0.3048 meters

在 清單 6 中,兩個轉(zhuǎn)換函數(shù)的實(shí)現(xiàn)演示了匿名函數(shù)聲明的兩種語法變體。每個函數(shù)只接受一個參數(shù)(asF 函數(shù)中的 this)。單參數(shù)函數(shù)很常見,以至于 Clojure 為它們的創(chuàng)建提供了語法糖,如 AsM 函數(shù)中所示,其中 % 是參數(shù)占位符。

協(xié)議創(chuàng)建了一種將方法(以函數(shù)形式)添加到現(xiàn)有類中的簡單解決方案。Clojure 還包含一些有用的宏,使您能夠?qū)⒁唤M擴(kuò)展整合在一起。例如,Compojure Web 框架(參見 參考資料)使用協(xié)議擴(kuò)展各種類型,以便它們 “知道” 如何呈現(xiàn)自身。清單 7 顯示了來自 Compojure 中的Renderable 的一段代碼:

清單 7. 通過協(xié)議擴(kuò)展許多類型

(defprotocol Renderable

(render [this request]

"Render the object into a form suitable for the given request map."))

(extend-protocol Renderable

nil

(render [_ _] nil)

String

(render [body _]

(-> (response body)

(content-type "text/html; charset=utf-8")))

APersistentMap

(render [resp-map _]

(merge (with-meta (response "") (meta resp-map))

resp-map))

IFn

(render [func request]

(render (func request)

; . . .

在 清單 7 中,Renderable 協(xié)議是使用單個 render 函數(shù)來定義的,該函數(shù)接受一個值和一個請求映射作為參數(shù)。Clojure 的 extend-protocol宏(它可用于將協(xié)議定義分組到一起)接受類型和實(shí)現(xiàn)對。在 Clojure 中,您可使用下劃線代替不關(guān)心的參數(shù)。在 清單 7 中,這個定義的可看見部分為 nil、String、APersistentMap 和 IFn(Clojure 中的函數(shù)的核心接口)提供了呈現(xiàn)指令。(該框架中還包含其他許多類型,但為節(jié)省空間,清單中省略了它們。)可以看到這在實(shí)踐中非常有用:對于您可能需要呈現(xiàn)的所有類型,您可將語義和擴(kuò)展放在一起定義。

評論
學(xué)計(jì)算機(jī)專業(yè)難嗎 學(xué)計(jì)算機(jī)專業(yè)好不好 學(xué)計(jì)算機(jī)有什么專業(yè) 學(xué)計(jì)算機(jī)有哪些專業(yè) 學(xué)計(jì)算機(jī)學(xué)習(xí)什么專業(yè)好 學(xué)計(jì)算機(jī)學(xué)什么專業(yè)好 學(xué)計(jì)算機(jī)學(xué)什么專業(yè) 初中生學(xué)什么技術(shù)好 NET軟件開發(fā)工程師 linux運(yùn)維工程師 運(yùn)維部門主管 高中生如何學(xué)IT 男孩學(xué)什么技術(shù)好就業(yè) 高考結(jié)束后適合學(xué)什么技術(shù) 深圳IT培訓(xùn)學(xué)校哪家好 計(jì)算機(jī)專業(yè)學(xué)什么 北大青鳥深圳嘉華培訓(xùn)怎么樣 北大青鳥學(xué)費(fèi)一年多少 黃貝北大青鳥 企業(yè)化帶班 高考畢業(yè)生學(xué)什么技術(shù)好找工作 高中生學(xué)什么專業(yè)好找工作 北大青鳥開學(xué)時間 北大青鳥紀(jì)錄片 翠竹北大青鳥 明日之星總決賽 高考落榜讀什么學(xué)校有好出路 2017專科錄取分?jǐn)?shù)線 現(xiàn)在什么專業(yè)就業(yè)前景好 200分能上什么學(xué)校 高考多少分能上專科學(xué)校 高中畢業(yè)后學(xué)什么好 高考沒被錄取怎么辦 校園形象代言人 北大青鳥學(xué)費(fèi)是多少 JAVA6 高考失利怎么辦 筍崗北大青鳥 華富北大青鳥 大合唱 明日之星節(jié)目 園嶺北大青鳥 北大青鳥什么時候開學(xué) 現(xiàn)在什么專業(yè)好就業(yè) 深圳北大青鳥學(xué)校的學(xué)費(fèi)是多少
好吊妞免费视频在线观看,久久亚洲国产人成综合网,久久精品国产2020,欧美精品综合在线
日本亚洲欧美在线AⅤ | 色欧美4477福利网在线观看 | 欧美一级成在线人 | 色五月婷婷导航在线观看 | 日本一区精品一本大道 | 亚洲精品有码在线观看 |