程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 面向對象語言概論(三)

面向對象語言概論(三)

編輯:關於C++

傳統的基於類的面向對象語言的一個主要特點就是inheritance, subclassing和subtyping之間的密不可分的聯系。很多的面向對象語言的語法,概念,就是從這三者而來的。比如說,通過subclassing, 你可以繼承父類的一些方法,而同時你又可以在子類中改寫父類的方法。這個改寫過的方法,通過subtyping, subsumption, 又可以從一個類型是父類的對象去調用。

但是,inheritance, subclassing, subtyping這三者並不是永遠和睦相處的。在一些場合,這三者之間的糾纏不清會妨礙到通過繼承或泛型得到的代碼重用。因此,人們開始注意到把這三者分離開來的可能性。區分subclassing和subtyping已經很常見了。而其它的一些方法還處於研究的階段。這一章我們將介紹這樣一些方法。

一,對象類型

在早期的面向對象語言中(如Simula), 類型的定義是和方法的實現是混合在一起的。這種方式違反了我們今天已經被廣泛認識到的把實現和規范(Specification) 分離的原則。這種分離得原則在開發是團隊進行的時候尤其顯得重要。

更近期一些的語言,通過引入不依賴於實現的對象類型來區分實現和規范。Modula-3以及其它如Java等的支持class和interface的語言都是采用的這種技術。

在本書中,我們開始引入InstanceTypeOf(cell)時,它代表的概念相當有限。看上去,它似乎只表示用new cell生成的對象的類型,於是,我們並不能用它來表示從其它類new出來的對象。但後來,當我們引入了subclassing, method overriding, subsumption和dynamic dispatch之後,事情變得不那麼簡單了。我們的InstanceTypeOf(cell)已經可以用來表示從cell的子類new出來的對象,這些對象可以包括不是cell類定義的屬性和方法。

如此看來,讓InstanceTypeOf(cell)依賴於一個具體的類似乎是不合理的。實際上,一個InstanceTypeOf(cell)類型的對象不一定會跟class cell扯上任何關系。

它和cell類的唯一共同之處只是它具有了所有cell類定義的方法的簽名(signature).

基於這種考慮,我們可以引入對象類型的語法:

針對cell類和reCell類的定義:

class cell is

var contents: Integer :=0;

method get(): Integer is

return self.contents;

end;

method set(n:Integer) is

self.contents := n;

end;

end;

subclass reCell of cell is

var backup: Integer := 0;

override set(n: Integer) is

self.backup := self.contents;

super.set(n);

end;

method restore() is

self.contents := self.backup;

end;

end;

我們可以給出這樣的對象類型定義:

ObjectType Cell is

var contents: Integer;

method get(): Integer;

method set(n:Integer);

end;

ObjectType ReCell is

var contents: Integer;

var backup: Integer;

method get(): Integer

method set(n: Integer);

method restore();

end;

這兩個類型的定義包括了所有cell類和reCell類定義的屬性和方法的類型,但卻並不包括實現。這樣,它們就可以被當作與實現細節無關的的接口以實現規范和實現的分離。兩個完全無關的類c和c’, 可以具有相同的類型Cell, 而Cell類型的使用者不必關心它使用的是c類還是c’類。

注意,我們還可以加入額外的類似繼承的語法來避免在ReCell裡重寫Cell裡的方法簽名。但那只是小節罷了。

二,分離Subclassing和Subtyping.

在我們上一章的討論中,subtype的關系是建立在subclass關系的基礎上的。但如果我們想要讓type獨立於class, 那麼我們也需要定義獨立於subclass的subtype.

在定義subtype時,我們又面臨著幾種選擇:subtype是由類型的組成結構決定的呢?還是由名字決定呢?

由類型的組成結構決定的subtype是這樣的:如果類型一具有了類型二的所有需要具備的屬性和方法,我們就說類型一是類型二的subtype.

由類型名字決定的subtype是這樣的:只有當類型一具有了類型二的所有需要具備的屬性和方法, 並且類型一被明確聲明為類型二的subtype時,我們才認可這種關系。

而如果我們的選擇是一,那麼那些屬性和方法是subtype所必需具備的呢?哪些是可有可無的呢?

由組成結構決定的subtype能夠在分布式環境和object persistence系統下進行類型匹配(譯者注:對這點,我也不甚明了。看來,紙造得還是不夠)。缺點是,如果兩個類型碰巧具有了相同的結構,但實際上卻風馬牛不相及,那就會造成錯誤。不過,這種錯誤是可以用一些技術來避免的。

相比之下,基於名字的subtype不容易精確定義,而且也不支持基於結構的subtype.

(譯者按,這裡,我無論如何和作者找不到同感。基於結構的subtype的缺點是一目了然,不過完美的避免的方法我卻看不出來。而基於名字的subtype為什麼就不能精確定義呢?C++/Java/C#, 所有流行的OO語言都只支持基於名字的subtype, 也沒有發現有什麼不夠靈活的地方。需要在不同名字但類似結構的類型之間架橋的話,adapter完全可以勝任嘛!)

目前,我們可以先定義一個簡單的基於結構的subtype關系:

對兩個類型O和O’,

O’ <: O 當 O’ 具有所有O類型的成員。O’可以有多於O的成員。

例如:ReCell <: Cell.

為了簡明,這個定義沒有考慮到方法的特化。

另外,當類型定義有遞歸存在的時候(類似於鏈表的定義),對subtype的定義需要額外地加小心。我們會在第九章及之後章節講到遞歸的時候再詳細說明。(譯者按:第九章啊?饒了我吧!想累死我啊?)

因為我們不關心成員的順序,這種subtype的定義自動地就支持多重的subtype.

比如說:

ObjectType ReInteger is

var contents: Integer;

var backup: Integer;

method restore();

end;

那麼,我們就有如下的subtype的關系:

ReCell <: Cell

ReCell <: ReInteger

(譯者按,作者的例子中沒有考慮到象interface不能包含數據域這樣的細節。實際上,如果我們支持對數據域的override, 而不支持shadowing -- 作者的基於結構的subtype語義確實隱含著這樣的邏輯― 那麼,interface裡包含不包含數據域就無關緊要了,因為令人頭疼的名字沖突問題已經不存在了)

從這個定義,我們可以得出:

如果c’是c的子類, 那麼ObjectTypeOf(c’) <: ObjectTypeOf(c)

注意,這個定義的逆命題並不成立,也就是說:

即使c’和c之間沒有subclass的關系,只要它們所定義的成員符合了我們subtype的定義,ObjectTypeOf(c’) <: ObjectTypeOf(c)仍然成立。

回過頭再看看我們在前一章的subclass-is-subtyping:

InstanceTypeOf(c’) <: InstanceTypeOf(c) 當且僅當 c’是c的子類

在那個定義中,只有當c’是c的子類時,ObjectTypeOf(c’) <: ObjectTypeOf(c)才能成立。

相比之下,我們已經部分地把subclassing和subtyping分離開了。Subclassing仍然是subtyping, 但subtyping不再一定要求是subclassing了。我們把這種性質叫做“subclassing-implies-subtyping”而不是“subclass-is-subtyping”了。

三,泛型 (Type Parameters)

一般意義上來說,泛型是一種把相同的代碼重用在不同的類型上的技術。它作為一個相對獨立於其它面向對象特性的技術,在面向對象語言裡已經變得越來越普遍了。我們這裡之所以討論泛型,一是因為泛型這種技術本身就很讓人感興趣,另外,也是因為泛型是一個被用來對付二元方法問題 (binary method problem) 的主要工具。

和subtyping共同使用,泛型可以用來解決一些在方法特化等場合由反協變帶來的類型系統的困難。考慮這樣一個例子:

我們有Person和Vegitarian兩種類型,同時,我們有Vegitable和Food兩種類型。而且,

Vegitable <: Food.

ObjectType Person is



method eat(food: Food);

end;

ObjectType Vegetarian is



method eat(food: Vegitable);

end;

這裡,從常識,我們知道一個Vegitarian是一個人。所以,我們希望可以有Vegetarian <: Person.

不幸的是,因為參數是反協變的,如果我們錯誤地認為Vegetarian <: Person, 根據subtype的subsumption原則,一個Vegetarian的對象就可以被當作Person來用。於是一個Vegetarian就可以錯誤地吃起肉來。

使用泛型技術,我們引入Type Operator (也就是,從一個類型導出另一個類型,概念上類似於對類型的函數)。

ObjectOperator PersonEating[F<:Food] is



method eat(food: F);

end;

ObjectOperator VegetarianEating[F<: Vegetable] is



method eat(food: F);

end;

這裡使用的技術被稱作Bounded Type Parameterization. (Trelli/Owl, Sather, Eiffel, PolyTOIL, Raptide以及Generic Java都支持Bounded Type Parameterization. 其它的語言,如C++, 只支持簡單的沒有類型約束的泛型)

F是一個類型參數,它可以被實例化成一個具體的類型。 類似於變量的類型定義,一個bound如F<:Vegitable限制了F只能被Vegitable及其子類型所實例化。所以,VegitarianEating[Vegitable], VegitarianEating[Carrot]都是合法的類型。而VegitarianEating[Beef]就不是一個合法的類型。類型VegitarianEating[Vegitable]是VegitarianEating的一個實例,同時它等價於類型Vegitarian. (我們用的是基於結構的subtype)

於是,我們有:

對任意F<:Vegitable, VegitarianEating[F] <: PersonEating[F]

對於原來的Vegitarian類型,我們有:

Vegetarian = VegetarianEating[Vegetable] <: PersonEating[Vegitable]

這種關系,正確地表達了“一個素食者是一個吃蔬菜的人”的概念。

除了Bounded Type Parameterization之外,還有一種類似的方法也可以解決這個素食者的問題。這種方法被叫做:Bounded Abstract Type

請看這個定義:

ObjectType Person is

Type F<: Food;



var lunch: F;

method eat(food: F);

end;

ObjectType Vegetarian is

Type F<: Vegitable;



var lunch: F;

method eat(food: F);

end;

這裡,F<:Food的意思是,給定一個Person, 我們知道他能吃某種Food, 但我們不知道具體是哪一種。這個lunch的屬性提供這個Person所吃的Food.

在創建Person對象時,我們可以先選定一個Food的subtype, 比如說,F=Dessert. 然後,用一個Dessert類型的變量賦給屬性lunch. 最後再實現一個eat(food:Dessert)的方法。

這樣,Vegetarian <: Person是安全的了。當你把一個Vegetarian當作一個Person處理時,這個Vegitarian可以安全地吃他自帶的午餐,即使你不知道他吃的是肉還是菜。

這種方法的局限在於,Person, Vegitarian只能吃他們自帶的午餐。你不能讓他們吃買來的午餐。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved