四,彻底划清界限(继续分离Subclassing和Subtyping)
在第二节我们讨论了部分分离Subclassing和subtyping的方法,即subclassing-implies-subtyping. 现今的许多面向对象语言,如Java, C#都是采用了这种技术。除此之外,还有一种进一步分离Subclassing和subtyping的方法。这种被称作inheritance-is-not-subtyping的方法通过完全割裂subclassing和subtyping之间的联系而在更大程度上方便了代码的重用。
它的产生很大程度上是由于人们想要使用在反协变位置上的Self类型 (如Self类型的参数)。当然,增大继承的能力的代价是subsumption的灵活性降低了。当Self类型出现在反协变的位置上时,subclass不再意味着subtype, 因此,subsumption也就不存在了。
下面请考虑这样两个类型:
ObjectType Max is
var n: Integer;
method max(other:Max): Max;
end;
ObjectType MinMax is
var n: Integer;
method max(other:MinMax): MinMax;
method min(other:MinMax): MinMax;
end;
再考虑两个类:
class MaxClass is
var n:Integer :=0;
method max(other: Self): Self is
if self.n > other.n then return self else return other end;
end;
end;
subclass MinMaxClass of MaxClass is
method min(other: Self): Self is
if self.n < other.n then return self else return other end;
end;
end;
方法min和max是二元的,因为它操作两个对象:self和other. other的类型是一个出现在反协变位置上的Self类型。
注意,方法max有一个反协变的参数类型Self, 并且它被从类MaxClass继承到了MinMaxClass.
很直观地,类MaxClass对应着类型Max;类MinMaxClass对应着类型MinMax. 为了精确地表示这种对应关系,我们必须针对包含使用Self类型的成员的类重新定义ObjectTypeOf,以便得到ObjectTypeOf(MaxClass) = Max, ObjectTypeOf(MinMaxClass) = MinMax。
为了使以上的等式成立,我们把类中的Self类型映射到ObjectType中的类型名称本身。我们同时让Self类型在继承的时候特化。
在本例中,当我们映射MinMaxClass的类型时,我们把继承来的max方法中的Self类型映射到MinMax类型。而对MaxClass中max方法的Self类型,我们使用Max类型。
如此,我们可以得到,任何MaxClass生成的对象,都具备Max类型。而任何MinMaxClass生成的对象都具备MinMax类型。
虽然MinMaxClass是MaxClass的子类,但这里MinMax却不是Max的子类型(subtype).
举个例子,如果我们假设subtype在这种情况下成立,那么,对以下的这个类:
subclass MinMaxClass’ of MinMaxClass is
override max(other: Self): Self is
if other.min(self) = other then return self else return other end;
end;
end;
根据我们对Self类型的映射规则和基于结构的subtype规则,我们知道,ObjectTypeOf(MinMaxClass’) = MinMax, 所以,对任何MinMaxClass’生成的对象mm’ ,我们可以知道mm’ : MinMax.
而如果MinMax <: Max成立,根据subsumption, 我们就能推出mm’ : Max.
于是当我们调用mm’.max(m)的时候,m可以是任何Max类型的对象。但是,当max的方法体调用other.min(self)的时候,如果这个other不具有min方法,这个方法就会失败。
由此可见,MinMax <: Max并不成立。
子类(subclass) 在使用反协变的Self类型时就不再具有subtype的性质了。
五,对象协议 (Object Protocol)
从上一节的讨论,我们看到对使用反协变Self类型的类,subclass不再是subtype了。这是一个令人失望的结果,毕竟很多激动人心的面向对象的优点是通过subtype, subsumption来实现的。
不过,幸运的是,虽然失去了subtype, 我们还是可以从中挖掘出来一些可以作为补偿的有用的东西的。只不过,不象subtype, 我们不能享受subsumption了。
下面就让我们来研究这种新的关系。
在第四节的MinMax的例子中,subtype不再成立;简单地使用泛型,引入
ObjectOperator P[M <: Max] is … end; 也似乎没有什么用。P[Max]虽然成立,但P[MinMax]却是不合法的,因为MinMax <: Max不成立。
但是,直观上看,任何支持MinMax这种协议的对象,也支持Max协议的 (虽然我们还不知道这个“协议”到底是个什么东西)。于是,似乎隐隐约约地又一个叫做“子协议”(subprotocol)的家伙在向我们招手了。
为了发现这个子协议的关系,让我们先定义两个type operator (还记得吗?就是作用在类型上的函数):
ObjectOperator MaxProtocol[X] is
var n: Integer;
method max(other: X) :X;
end;
ObjectOperator MinMaxProtocol[X] is
var n:Integer;
method max(other: X):X;
method min(other: X):X;
end;
这样,Max = MaxProtocol[Max], MinMax = MinMaxProtocol[MinMax]
更一般地说,我们可以定义:
什么 = 什么-Protocol[什么]
还记得lamda-calculus里的fixpoint吗?给定一个函数F, F(fixpoint(F)) = fixpoint(F)
而在我们这个子协议的type operator里,如果我们认为type operator是作用于类型的函数的话, 那么这个“什么”,就是“什么-Protocol”函数的fixpoint啊!
也就是说:
什么= fixpoint (什么-Protocol).
除了以上的fixpoint的性质,我们还发现了存在于Max和MinMax之间的关系。
首先,MinMax是MaxProtocol的一个post-fixpoint,即:
MinMax <: MaxProtocol[MinMax]
其次,我们可以看出:
MinMaxProtocol[Max] <: MaxProtocol[Max]
MinMaxProtocol[MinMax] <: MaxProtocol[MinMax]
最后,如果我们用<::来表示一个更高阶的子类型关系:
P <:: P’ 当且仅当 P[T] <: P’[T]
那么,MinMaxProtocol <:: MaxProtocol.
对于子协议的定义,我们可以采取上面的<::的定义,即:
如果S-Protocol<::T-Protocol, 那么我们称类型S和类型T之间是子协议关系。(1)
我们也可以不用这个高阶的关系,仍然使用<:这个subtype的关系:
如果S<:T-Protocol[S], 那么我们称类型S和类型T之间是子协议关系。(2)
其实,第一个定义似乎更直观一点,它更明确地显示出子协议关系是作用于类型上的函数(type operator)之间的关系,而不是类型之间的关系。
使用泛型技术,如果我们的某一个类型需要一个实现MaxProtocol的类型来实例化的话,我们可以采用下面两种方法中的一种:
ObjectOperator P1[X <: MaxProtocol[X]] is … end; (1)
ObjectOperator P2[P <:: MaxProtocol] is … end; (2)
这两种方法在表达能力上是相同的。第一种方法叫做F-bounded parameterization. (译者按,Generic Java据说就采用了这个方法);第二种方法叫做 higher-order bounded parameterization.
对于具体语言的实现,为了方便,我们可以隐藏这个type operator. 语法上可以直接对类型支持subprotocol的关系(用<#来表示)。
对我们的MinMax的例子来说,我们就有:
MinMax <# Max.
(译者按,绕了一大圈,什么fixpoint啊,post-fixpoint啊,什么高阶关系啦,希望没把你绕晕。其实,你只需要记住MinMax <# Max, 就八九不离十了,呵呵)
<#这个关系并不具有subsumption的性质,所以,你不能指望从它身上得到传统OO里面的多态。但是,与泛型相结合,它却是非常有用的。
比如说,我们可以对我们的所有你用来当作参数传给我的基于GP的快速排序模板函数给出这样的约束:用来实例化这个模板函数的的类型必须支持Comparable协议。
(译者按,使用它对加强C++中的模板的类型安全会很有用。其实,对使用gp的程序来说,也许子协议约束比子类型约束更合理。子类型的sumbsumption要求多态,即dynamic dispatch。但很多时候,我们并不一定需要类型参数支持多态。请参看我的拙文:http://www.allaboutprogram.com/bb/viewtopic.php?t=255)
思考题:
1.Java是支持Covariant的Array的。你可以把一个String[] 类型的对象当作一个Object[]类型来使用。
那么,Java的covariant array是类型安全的吗?为什么?
2.大家都知道经典的矩形和正方形之间的类型关系吧?在支持get, set的正方形和矩形之间,并不存在subtype的关系,这是因为subsumption对get或set操作是不安全的。但是,是否正方形和矩形之间就不可能有subtype的关系呢?如果矩形的类型只支持get, 结果会是什么?如果正方形只支持set, 结果又是什么呢?
下章预告:
基于对象的面向对象语言。