展现子类型多态的魅力----从面向类型的观点看多态(二)

 

                      展现子类型多态的魅力

                              ----从面向类型的观点看多态(二)

 

 

多态的依附性

类型的一致性是多态的核心。对象上的每一个引用,静态的类型检查器都要确认这样的依附和其对象的层次是一致的。当一个引用成功的依附于另一个不同的对象时,有趣的多态现象就产生了。(严格的说,对象类型是指类的定义。)你也可以把几个不同的引用依附于同一个对象。在开始更有趣的场景前,我们先来看一下下面的情况为什么不会产生多态。

 

多个引用依附于一个对象

2和图3描述的例子是把两个及两个以上的引用依附于一个对象。虽然Derived2对象在被依附之后仍保持了变量的类型,但是,图3中的Base类型的引用依附之后,其功能减少了。结论很明显:把一个基类的引用依附于派生类的对象之上会减少其能力。

 

一个开发这怎么会选择减少对象能力的方案呢?这种选择是间接的。假设有一个名为ref的引用依附于一个包含如下方法的类的对象:

 

public String poly1( Base base )

{

  return base.m1();

}

 

用一个Derived2的参数调用poly(Base)是符合参数类型检查的:

 

ref.poly1( derived2 );

 

方法调用把一个本地Base类型的变量依附在一个引入的对象上。所以,虽然这个方法只接受Base类型的参数,但Derived2对象仍是允许的。开发这就不必选择丢失功能的方案。从人眼在通过Derived2对象时所看到的情况,Base类型引用的依附导致了功能的丧失。但从执行的观点看,每一个传入poly1(Base)的参数都认为是Base的对象。执行机并不在乎有多个引用指向同一个对象,它只注重把指向另一个对象的引用传给方法。这些对象的类型不一致并不是主要问题。执行器只关心给运行时的对象找到适当的实现。面向类型的观点展示了多态的巨大能力。

 

附于多个对象的引用

让我们来看一下发生在poly1(Base)中的多态行为。下面的代码创建了三个对象,并通过引用传给poly1(Base):

 

Derived2 derived2 = new Derived2();

Derived derived = new Derived();

Base base = new Base();

 

String tmp;

tmp = ref.poly1( derived2 );     // tmp is "Derived.m1()"

tmp = ref.poly1( derived );      // tmp is "Derived.m1()"

tmp = ref.poly1( base );         // tmp is "Base.m1()"

 

poly1(Base)的实现代码是调用传进来的参数的m1()方法。图3和图4展示了把三个类的对象传给方法时,面向类型的所使用的体系结构。

 4:将Base引用指向Derived类,以及Base对象

 

请注意每个图中方法m1()的映射。图3中,m1()调用了Derived类的代码;上面代码中的注释标明了ploy1(Base)调用Derived.m1()。图4Derived对象调用的仍然是Derived类的m1()方法。最后,图4中,Base对象调用的m1()Base类中定义的代码。

 

多态的魅力何在?再来看一下poly1(Base)的代码,它可以接受任何属于Base类范畴的参数。然而,当他收到一个Derived2的对象时,它实际上却调用了Derived版本的方法。当你根据Base类派生出其他类时,如DerivedDerived2poly1(Base)都可以接受这些参数,并作出选择调用合适的方法。多态允许你在完成poly1(Base)后扩展它的用途。

 

这看起来当然很神奇。基本的理解展示了多态的内部工作原理。在面向类型的观点中,底层的对象所实现的代码是非实质性的。重要的是,类型检查器会在编译期间为每个引用选择合适的代码以实现其方法。多态使开发者运用面向类型的观点,不考虑实现的细节。这样有助于把类型和实现分离(实际用处是把接口和实现分离)

 

对象的接口

多态依赖于类型和实现的分离,多用来把接口和实现分离。但下面的观点好像把Java的关键字interface搞得很糊涂。

 

更为重要的使开发者们怎样理解短语the interface to an object",典型地,根据上下文,这个短语的意思是指一切对象类中所定义的方法,至一切对象公开的方法。这种倾向于以实现为中心的观点较之于面向类型的观点来说,使我们更加注重于对象在运行期的能力。图3中,引用面板的对象表面被标志成"Derived2 Object"。这个面板上列出了Derived2对象的所有可用的方法。但是要理解多态,我们必须从实现这一层次上解放出来,并注意面向类型的透视图中被标为"Base Reference"的面板。在这一层意思上,引用变量的类型指明了一个对象的表面。这只是一个表面,不是接口。在类型一致的原则下,我们可以用面向类型的观点,为一个对象依附多个引用。对interface to an object这个短语的理解没有确定的理解。

 

在类型概念中,the interface to an object refers 引用了面向类型观点的最大可能----如图2的情形。把一个基类的引用指向相同的对象缩小了这样的观点----如图3所示。类型概念能使人获得把对象间的相互作用同实现细节分离的要领。相对于一个对象的接口,面向类型的观点更鼓励人们去使用一个对象的引用。引用类型规定了对象间的相互作用。当你考虑一个对象能做什么的时候,只需搞明白他的类型,而不需要去考虑他的实现细节。

 

Java接口

以上所谈到的多态行为用到了类的继承关系所建立起来的子类型关系。Java接口同样支持用户定义的类型,相对地,Java的接口机制启动了建立在类型层次结构上的多态行为。假设一个名为ref的引用变量,并使其指向一个包含一下方法的类对象:

 

public String poly2( IType iType )

{

  return iType.m3();

}

 

为了弄明白poly2(IType)中的多态,以下的代码从不同的类创建两个对象,并分别把他们传给poly2(IType)

 

Derived2 derived2 = new Derived2();

Separate separate = new Separate();

 

String tmp;

tmp = ref.poly2( derived2 );       // tmp is "Derived.m3()"

tmp = ref.poly2( separate );       // tmp is "Separate.m3()"

 

上面的代码类似于关于poly1(Base)中的多态的讨论。poly2(IType)的实现代码是调用每个对象的本地版本的m3()方法。如同以前,代码的注释表明了每次调用所返回的CString类型的结果。图5表明了两次调用poly2(IType)的概念结构:

 

5:指向Derived2Separate对象的IType引用

 

方法poly1(Base)poly2(IType)中所表现的多态行为的相似之处可以从透视图中直接看出来。把我们在实现在一层上的理解再提高一层,就可以看到这两段代码的技巧。基类的引用指向了作为参数传进的类,并且按照类型的限制调用对象的方法。引用既不知道也不关心执行哪一段代码。编译期间的子类型关系检查保证了通过的对象有能力在被调用的时候选择合适的实现代码。

 

然而,他们在实现层上有一个重要的差别。在poly1(Base)的例子中(图3和图4),Base-Derived-Derived2的类继承结构为子类型关系的建立提供了条件,并决定了方法去调用哪段代码。在poly2(IType)的例子中(如图5),则是完全不同的动态发生的。Derived2Separate不共享任何实现的层次,但是他们还是通过IType的引用展示了多态的行为。

 

这样的多态行为使Java的接口的功能的重大意义显得很明显。图1中的UML类图说明了DerivedBaseIType的子类型。通过完全脱离实现细节的类型的定义方法,Java实现了多类型继承,并且不存在Java所禁止的多继承所带来的烦人的问题。完全脱离实现层次的类可以按照Java接口实现分组。在图1中,接口ITypeDerived,Separate以及这类型的其他子类型应该划为一组。

 

按照这种完全不同于实现层次的分类方法,Java的接口机制是多态变得很方便,哪怕不存在任何共享的实现或者复写的方法。如图5所示,一个IType的引用,用多态的方法访问到了Derived2Separate对象的m3()方法。

 

再次探讨对象的接口

注意图5中的Derived2Separate对象的对m1()的映射方法。如前所述,每一个对象的接口都包含方法m1()。但却没有办法用这两个对象使方法m1()表现出多态的行为。每一个对象占有一个m1()方法是不够的。必须存在一个可以操作m1()方法的类型,通过这个类型可以看到对象。这些对象似乎是共享了m1()方法,但在没有共同基类的条件下,多态是不可能的。通过对象的接口来看多态,会把这个概念搞混。

 

结论

从全文所述的面向对象多态所建立起来的子类型多态,你可以清楚地认识到这种面向类型的观点。如果你想理解子类型多态的思想,就应该把注意力从实现的细节转移到类型的上。类型把对象分成组,并且管理着这些对象的接口。类型的继承层次结构决定了实现多态所需的类型关系。

 

有趣的是,实现的细节并不影响子类型多态的层次结构。类型决定了对象调用什么方法,而实现则决定了对象怎么执行这个方法。也就是说,类型表明了责任,而负责实施的则是具体的实现。将实现和类型分离后,我们好像看到了这两个部分在一起跳舞,类型决定了他的舞伴和舞蹈的名字,而实现则是舞蹈动作的设计师。

关于作者:

Wm.Paul Rogers是工作于Lutris Technologies的资深工程师和应用程序开发者,他利用开源代码的Java/XML应用服务器Enhydra来提出解决方案。他于1995年秋,在MBARIMonterey Bay Aquarium Research Institute)研究海洋生物学的时候开始运用Java,那时,他负责研究如何用新技术来研究海洋科学。Paul运用面向对象技术已有九之多。

 

参考文献: 

 

                               水平有限,不妥之处敬请指教

                                      Email:rdqjuven@hotmail.com

                                         qq:1735739

                                 

 

  • 1
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

rdqjuven

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值