副标题#e#
4. Subsumption和Dynamic Dispatch (译者按:呵呵,黔驴技穷,找不到符合的翻译了)
从上述的几个例子来看,好像子类只是用来从父类借用一些界说,以制止反复。可是,当我们思量到subsumption, 工作就有些差异了。什么是Subsumption呢?请看下面这个例子:
var myCell: InstanceTypeOf(cell) := new cell;
var myReCell: InstanceTypeOf(reCell) := new reCell;
procedure f(x: InstanceTypeOf(cell)) is … end;
再看下面这段代码:
myCell := myReCell;
f(myReCell);
在这两行代码中,头一行把一个InstanceTypeOf(reCell)范例的变量赋值给一个InstanceTypeOf(cell)的变量。而第二行则用InstanceTypeOf(reCell)范例的变量作为参数通报给一个参数范例为InstanceTypeOf(cell)的函数。
这种用法在雷同Pascal的语言中是不正当的。而在面向工具的语言中,依据以下的法则,它则是完全正确的用法。该法则凡是被叫做subtype polimorphism, 即子范例多态(译者按:其实subtyping应该是OO语言最区别于其它语言的处所了)
假如c’是c的子类,而且o’是c’的一个实例,那么o’也是c的一个实例。
更严格地说:
假如c’是c的子类,而且o’: InstanceTypeOf(c’),那么o’: InstanceTypeOf( c ).
仔细阐明上面这条法则,我们可以在InstanceTypeOf的范例之间引入一个满意自反和通报性的子范例干系, 我们用<:标记来暗示。(译者按:自反就是说, 对任何a, a 干系 a都创立,好比说,数学里的相等干系就是自反的。而通报性是说,假如a 干系 b, b 干系c, 就能推出a 干系c。 大于,小于等干系都是具备通报性的)
那么上面这条法则可以被拆成两条法则:
1. 对任何a: A, 假如 A <: B, 那么 a: B.
2. InstanceTypeOf(c’) <: InstanceTypeOf(c) 当且仅当 c’是c的子类
第一条法则被叫做Subsumption. 它是判定子范例(留意,是subtype, 不是subclass)的独一尺度。
第二条法则可以叫做subclassing-is-subtyping (子类就是子范例,绕嘴吧?)
一般来说,担任都是和subclassing相关的,所以这条法则也可以叫做:inheritance-is-subtyping (担任就是子范例)
所有的面向工具语言都支持subsumption (可以说,没有subsumption, 就不成为面向工具)。
大部门的基于类的面向工具语言也并不区分subclassing和subtyping. 可是,一些最新的面向工具语言则采纳了把subtyping和subclassing分隔的要领。也就是说,A是B的子类,但A类的工具却不行以看成B类的工具来利用。(译者按:有点象C++里的私有担任,但内容比它富厚)
#p#副标题#e#
好吧,关于区分subclassing和subtyping, 我们后头会讲到。
下面,让我们从头转头来看看这个procedure f. 在subsumption的环境下,下面这个代码的动态语义是什么呢?
Procedure f(x: InstanceTypeOf(cell)) is
x.set(3);
end;
f(myReCell);
当myReCell被看成InstanceTypeOf(cell)的工具传入f的时候,x.set(3)毕竟是挪用哪一个版本的set要领呢?是界说在cell中的谁人set照旧界说在reCell中的谁人呢?
这时,我们有两种选择,
1. Static dispatch (凭据编译时的范例来抉择)
2. Dynamic dispatch (凭据工具运行时真正范例来抉择)
(译者按,熟悉C++的伴侣们必然微笑了,这再简朴不外了。)
static dispatch没什么可说的。
dynamic dispatch却有一个有趣的属性。那就是,subsumption必然不能影响工具的状态。假如你在subsumption的时候,改变了这个工具的状态,好比象C++中的工具切片,那么动态理会的要领就大概会失败。
亏得,这个属性无论对语义,照旧对效率,都是很有长处的。
(译者按,C++中的object slicing会把新的工具的vptr初始化成它本身范例的vtable指针, 所以不存在动态理会的问题。但实际上,工具切片基础不能叫做subsumption。
详细语言实现中,如C++, 固然subsumption不会改变工具内部的状态,但指针的值却是大概会变革的。这也是一个让人讨厌的对象,但 C++ vtable的方案却只能这样。有一种变种的vtable要领,可以制止指针的变革,也更高效。我们会在别的的文章中叙述这种要领。)
5. 赛翁失马 (关于范例信息)
固然subsumption并不改变工具的状态,在一些语言里(如Java), 它甚至没有任何运行时开销。可是,它却使我们丢掉了一些静态的范例信息。
好比说,我们有一个范例InstanceTypeOf(Object), 而Object类里没有界说任何属性和要领。我们又有一个类MyObject, 它担任自Object。那么当我们把MyObject的工具看成InstanceTypeOf(Object)范例来处理惩罚的时候,我们就获得了一个什么对象也没有的没用的空工具。
#p#分页标题#e#
虽然,假如我们思量一个不那么极度的环境,好比说,Object类内里界说了一个要领f, 而MyObject对要领f做了重载,那么, 通过dynamic dispatch, 我们照旧可以间接地操纵MyObject中的属性和要领的。这也是面向工具设计和编程的典范要领。
从一个purist的角度看(译者按,很不幸,我就是一个purist), dynamic dispatch是独一你应该用来操纵已经被subsumption忘掉的属性和要领的对象。它优雅,安详,所有的荣耀都归于dynamic dispatch!!! (译者按,这句话是我说的)
不外,让purist们失望的是,大部门语言照旧提供了一些在运行时查抄工具范例,并从而操纵被subsumption遗忘的属性和要领。这种要领一般被叫做RTTI(Run Time Type Identification)。如C++中的dynamic_cast, 或Java中的instanceof.
脚踏实地地说,RTTI是有用的。(译者按,典范的存在就是公道的强盗逻辑,气死我了!)。但因为一些理论上以及要领论上的原因,它被认为是粉碎了面向工具的纯洁性。
首先,它粉碎了抽象,使一些原来不该该被利用的要领和属性被不正确地利用。
其次,因为运行时范例的不确定性,它有效地把措施变得更懦弱。
第三点,也许是最重要的一点,它使你的措施缺乏扩展性。当你插手了一个新的范例时,你也许需要仔细阅读你的dynamic_cast或instanceof的代码,须要时窜改它们,以担保这个新的范例的插手不会导致问题。而在这个进程中,编译器将不会给你任何辅佐。
许多人一提到RTTI, 老是偏重于它的运行时的开销。可是,对比于要领论上的缺点,这点运行时的开销真是无足轻重的。
而在purist的框架中(译者按,吸一口吻,目视远方,做深沉状),新的子类的插手并不需要窜改已有的代码。
这是一个很是好的利益,尤其是当你并不拥有全部源代码时。
总的来说,固然RTTI (也叫type case)好像是不行制止的一种特性,但因为它的要领论上的一些缺点,它必需被很是审慎的利用。本日面向工具语言的范例系统中的许多对象就是发生于制止RTTI的各类尽力。
好比有些巨大的范例系统中可以在参数和返回值上利用Self范例来制止RTTI. 这点我们后头会先容到。
6.协变,反协变和压根儿稳定 (Covarance, Contravariance and Invariance)
在下面的几个小节里,我们来先容一种制止RTTI的范例技能。在此之前,我们先来先容“协变”,“反协变”和“压根儿稳定”的观念。
协变
首先,让我们来看一个Pair范例: A*B
这个范例支持一个getA()的操纵以返回这个Pair中的A元素。
给定一个A’ <: A, 那么,我们可以说A’*B <: A*B。
为什么呢?我们可以用Subsumption的属性加以证明:
假设我们有一个A’*B范例的工具a’*b, 这里,a’:A’, b:B, a’*b <: A’*B
那么,因为,A’ <: A, 从subsumption, 我们可以知道a’:A, getA():A 所以, a’*b<: A*B
这样,我们就界说A*B这个范例对付A是协变的。
同理,我们也可以证明A*B对付B也是协变的。
正规一点说,Covariance是这样界说的:
给定L(T), 这里,范例L是通过范例T组合成的。那么,
假如 T1 <: T2 可以或许推出 L(T1) <: L(T2), 那么我们就说L是对T协变的。
反协变
请看一个函数: A f(B b); (用functional language 的界说也许更简捷, 即f: B->A)
那么,给定一个B’ <: B, 在B->A 和 B’->A之间有什么样的subtype干系呢?
可以证明,B->A <: B’->A 。
基于篇幅,我们不再做推导。
所以,函数的参数范例是反协变的。
Contravariance的正规点的界说是这样的:
给定L(T), 这里,范例L是通过范例T组合成的。那么,
假如 T1 <: T2 可以或许推出 L(T2) <: L(T1), 那么我们就说L是对T反协变的。
同样,可以证明,函数的返回范例是协变的。
压根儿稳定
那么我们再思量函数g: A->A
这里,A既呈此刻参数的位置,又呈此刻返回的位置,可以证明,它既不是协变的,也不是反协变的。
对付这种既不是协变的,也不是反协变的环境,我们称之为Invariance (译者按:“压根儿稳定”是我编的,这么老土的翻译,列位不必卖力)
值得留意的是,对付第一个例子中的Pair范例,假如我们支持setA(A), 那么,Pair就酿成Invariance了。
7.要领特化 (Method Specialization)
在我们前面临subclass的接头中,我们采纳了一种最简朴的override的法则,那就是,overriding的要领必需和overriden的要领有沟通的signature.
#p#分页标题#e#
可是,从范例安详的角度来说,这并不是必需的。应用我们前面接头的协变和反协变的常识,我们完全可以让要领的返回范例协变,让要领的参数范例反协变。
这样,只要A <: A’, B’ <: B, 下面的代码就是正当的:
class c is
method m(x:A):B is … end;
method m1(x1:A1):B1 is … end;
end;
subclass c’ of c is