軟件設(shè)計(jì)師知識(shí)點(diǎn):面向?qū)ο笳Z(yǔ)言概論(二)

字號(hào):

4. Subsumption和Dynamic Dispatch (譯者按:呵呵,黔驢技窮,找不到合適的翻譯了)
    從上述的幾個(gè)例子來(lái)看,似乎子類只是用來(lái)從父類借用一些定義,以避免重復(fù)。但是,當(dāng)我們考慮到subsumption, 事情就有些不同了。什么是Subsumption呢?請(qǐng)看下面這個(gè)例子:
    var myCell: InstanceTypeOf(cell) := new cell;
    var myReCell: InstanceTypeOf(reCell) := new reCell;
    procedure f(x: InstanceTypeOf(cell)) is … end;
    再看下面這段代碼:
    myCell := myReCell;
    f(myReCell);
    在這兩行代碼中,頭一行把一個(gè)InstanceTypeOf(reCell)類型的變量賦值給一個(gè)InstanceTypeOf(cell)的變量。而第二行則用InstanceTypeOf(reCell)類型的變量作為參數(shù)傳遞給一個(gè)參數(shù)類型為InstanceTypeOf(cell)的函數(shù)。
    這種用法在類似Pascal的語(yǔ)言中是不合法的。而在面向?qū)ο蟮恼Z(yǔ)言中,依據(jù)以下的規(guī)則,它則是完全正確的用法。該規(guī)則通常被叫做subtype polimorphism, 即子類型多態(tài)(譯者按:其實(shí)subtyping應(yīng)該是OO語(yǔ)言最區(qū)別于其它語(yǔ)言的地方了)
    如果c’是c的子類,并且o’是c’的一個(gè)實(shí)例,那么o’也是c的一個(gè)實(shí)例。
    更嚴(yán)格地說(shuō):
    如果c’是c的子類,并且o’: InstanceTypeOf(c’),那么o’: InstanceTypeOf( c ).
    仔細(xì)分析上面這條規(guī)則,我們可以在InstanceTypeOf的類型之間引入一個(gè)滿足自反和傳遞性的子類型關(guān)系, 我們用<:符號(hào)來(lái)表示。(譯者按:自反就是說(shuō), 對(duì)任何a, a 關(guān)系 a都成立,比如說(shuō),數(shù)學(xué)里的相等關(guān)系就是自反的。而傳遞性是說(shuō),如果a 關(guān)系 b, b 關(guān)系c, 就能推出a 關(guān)系c。 大于,小于等關(guān)系都是具備傳遞性的)
    那么上面這條規(guī)則可以被拆成兩條規(guī)則:
    1. 對(duì)任何a: A, 如果 A <: B, 那么 a: B.
    2. InstanceTypeOf(c’) <: InstanceTypeOf(c) 當(dāng)且僅當(dāng) c’是c的子類
    第一條規(guī)則被叫做Subsumption. 它是判斷子類型(注意,是subtype, 不是subclass)的標(biāo)準(zhǔn)。
    第二條規(guī)則可以叫做subclassing-is-subtyping (子類就是子類型,繞嘴吧?)
    一般來(lái)說(shuō),繼承都是和subclassing相關(guān)的,所以這條規(guī)則也可以叫做:inheritance-is-subtyping (繼承就是子類型)
    所有的面向?qū)ο笳Z(yǔ)言都支持subsumption (可以說(shuō),沒(méi)有subsumption, 就不成為面向?qū)ο?。
    大部分的基于類的面向?qū)ο笳Z(yǔ)言也并不區(qū)分subclassing和subtyping. 但是,一些最新的面向?qū)ο笳Z(yǔ)言則采取了把subtyping和subclassing分開的方法。也就是說(shuō),A是B的子類,但A類的對(duì)象卻不可以當(dāng)作B類的對(duì)象來(lái)使用。(譯者按:有點(diǎn)象C++里的私有繼承,但內(nèi)容比它豐富)
    好吧,關(guān)于區(qū)分subclassing和subtyping, 我們后面會(huì)講到。
    下面,讓我們重新回頭來(lái)看看這個(gè)procedure f. 在subsumption的情況下,下面這個(gè)代碼的動(dòng)態(tài)語(yǔ)義是什么呢?
    Procedure f(x: InstanceTypeOf(cell)) is
     x.set(3);
    end;
    f(myReCell);
    當(dāng)myReCell被當(dāng)作InstanceTypeOf(cell)的對(duì)象傳入f的時(shí)候,x.set(3)究竟是調(diào)用哪一個(gè)版本的set方法呢?是定義在cell中的那個(gè)set還是定義在reCell中的那個(gè)呢?
    這時(shí),我們有兩種選擇,
    1. Static dispatch (按照編譯時(shí)的類型來(lái)決定)
    2. Dynamic dispatch (按照對(duì)象運(yùn)行時(shí)真正類型來(lái)決定)
    (譯者按,熟悉C++的朋友們一定微笑了,這再簡(jiǎn)單不過(guò)了。)
    static dispatch沒(méi)什么可說(shuō)的。
    dynamic dispatch卻有一個(gè)有趣的屬性。那就是,subsumption一定不能影響對(duì)象的狀態(tài)。如果你在subsumption的時(shí)候,改變了這個(gè)對(duì)象的狀態(tài),比如象C++中的對(duì)象切片,那么動(dòng)態(tài)解析的方法就可能會(huì)失敗。
    好在,這個(gè)屬性無(wú)論對(duì)語(yǔ)義,還是對(duì)效率,都是很有好處的。
    (譯者按,C++中的object slicing會(huì)把新的對(duì)象的vptr初始化成它自己類型的vtable指針, 所以不存在動(dòng)態(tài)解析的問(wèn)題。但實(shí)際上,對(duì)象切片根本不能叫做subsumption。
    具體語(yǔ)言實(shí)現(xiàn)中,如C++, 雖然subsumption不會(huì)改變對(duì)象內(nèi)部的狀態(tài),但指針的值卻是可能會(huì)變化的。這也是一個(gè)讓人討厭的東西,但 C++ vtable的方案卻只能這樣。有一種變種的vtable方法,可以避免指針的變化,也更高效。我們會(huì)在另外的文章中闡述這種方法。)
    5. 賽翁失馬 (關(guān)于類型信息)
    雖然subsumption并不改變對(duì)象的狀態(tài),在一些語(yǔ)言里(如Java), 它甚至沒(méi)有任何運(yùn)行時(shí)開銷。但是,它卻使我們丟掉了一些靜態(tài)的類型信息。
    比如說(shuō),我們有一個(gè)類型InstanceTypeOf(Object), 而Object類里沒(méi)有定義任何屬性和方法。我們又有一個(gè)類MyObject, 它繼承自O(shè)bject。那么當(dāng)我們把MyObject的對(duì)象當(dāng)作InstanceTypeOf(Object)類型來(lái)處理的時(shí)候,我們就得到了一個(gè)什么東西也沒(méi)有的沒(méi)用的空對(duì)象。
    當(dāng)然,如果我們考慮一個(gè)不那么極端的情況,比如說(shuō),Object類里面定義了一個(gè)方法f, 而MyObject對(duì)方法f做了重載,那么, 通過(guò)dynamic dispatch, 我們還是可以間接地操作MyObject中的屬性和方法的。這也是面向?qū)ο笤O(shè)計(jì)和編程的典型方法。
    從一個(gè)purist的角度看(譯者按,很不幸,我就是一個(gè)purist), dynamic dispatch是你應(yīng)該用來(lái)操作已經(jīng)被subsumption忘掉的屬性和方法的東西。它優(yōu)雅,安全,所有的榮耀都?xì)w于dynamic dispatch?。?! (譯者按,這句話是我說(shuō)的)