“还有两种继承方法?”我是真正被VB.NET的强大功能所折服了。求知的渴望驱使着我向大李露出了一个最灿烂的笑容,“行了,别傻笑了,我告诉你不就成了。”大李不禁也笑了起来。
“刚才我说到‘脆弱的基类’时,就提到实现继承最大的问题,就在于基类与派生类之间的关系过于紧密,还记得吧?基类实现细节往往会泄露出来,这不是我们愿意看到的封装情况,所以有很多程序设计师就想尽方法来改进这一问题,其中最出名的,就是COM了。”
“COM?我在VB6中经常用呀,是一种组件形式呀。”我不是太明白大李的话,这和面向对象的继承有什么关系?
“你说的不错,正是通过组件的这种封装方式,我们就可以把实现继承局限于在组件内部使用,而我们使用组件时,就不用理会它内部是什么,怎么实现的。这就可以避免不可预测地对基类的修改。我们把利用组件的组织程序方法称为面向组件程序设计,但这也是一种面向对象的设计方法,不过是更具强制性。组件支持组件内部的实现继承,还支持接口继承。”
“接口继承,我不是太清楚。不过,接口我是清楚的,就是组件开放的属性、方法与事件、公用变量的定义方法。我在VB6中也接触过接口编程,不是太方便,好象要把定义了方法却没有写实现过程的类编译成DLL文件,VB6会自动将它创建为接口,不过只能是隐藏的,不是显式定义的。”我回忆了一下说。
大李微微点了点头说:“从面向对象的观点来看,接口就是对象的外观,而对象实际的工作方式就是实现。把接口与实现分离开就是我们要进行封装的动机。用户只能通过接口来操作,但是看不到具体的实现的代码。”
大李顿了一顿,然后接着说:“VB.NET 以前的 Visual Basic 版本可以使用接口,但不能直接创建它们。VB.NET 却是允许可以用Interface 语句定义真正的接口的喔!”
此言一出,真让我大吃一惊。“我们也可以直接定义接口吗?”
“当然,”大李说,“在VB.NET中,和类一样,接口也可以定义属性、方法和事件。但正如我刚才说到的,与类不同的是,接口并不提供实现。现在的接口,是由类来实现的,并从类中被定义为单独的实体。”
大李手指在桌面上重重的敲了一下,加强了一下语气:“我们可以这样来理解,接口表示的是一种约定。实现接口的类必须严格按其定义来实现接口的每个方面。有了接口,就可以将功能定义为一些紧密相关成员的小组。可以在不危害现有代码的情况下,开发接口的增强型实现,从而使兼容性问题最小化。也可以在任何时候通过开发附加接口和实现来添加新的功能。虽然接口实现可以进化,但接口本身一旦被发布就不能再更改。对已发布的接口进行更改会破坏现有的代码。若把接口视为约定,很明显约定双方都各有其承担的义务。接口的发布者同意不再更改该接口,接口的实现者则同意严格按设计来实现接口。”
“也就是说,在VB.NET中,接口是用类来实现的,就象是个抽象类,只是关键字用的是Interface,不是class,对吗?”我还是很好奇。
“接口的实现可以是类,也可以是结构。接口的定义用的是Interface关键字,实现时用的是Implements关键字”大李淡淡的一句话,使我在心中开始回忆起类和结构的差别来(详见前文《类和结构》)。
大李接着跟我解说:“接口的成员包括其成员声明引入的成员和从其基接口继承的成员。只有嵌套类型、方法、属性和事件才能作为接口成员。方法和属性不能有实体。接口成员隐式地定为是Public,而且不能指定访问修饰符。接口自已倒是可以添加修饰符。”大李跟着看了我一眼,就在电脑上开始写起示例来:
Public Interface IHenry Sub subX(ByVal x As Integer) Function fcnY(ByVal y As Integer) As Long Property proZ() As String End Interface Public Class CHenry Implements IHenry Private z1 As String Sub subX(ByVal x As Integer) Implements IHenry.subX '填写实现代码 End Sub Function fcnY(ByVal y As Integer) As Long Implements IHenry.fcnY '填写实现代码 End Function Property proZ() As String Implements IHenry.proZ Get Return z1 End Get Set(ByVal Value As String) z1 = Value End Set End Property End Class |
大李指着代码说:“你看,我用Interface定义了一个接口IHenry(笔者注:一般来说,接口的命名第一个字母为I,这并没有什么强制的含义,但却是一个通用的命名规则),内含三个方法与属性的定义,但并没有实现;实现代码写在CHenry类中。你可以按我刚才说的约定的思路来理解,IHenry接口其实就是一个合约的提纲,CHenry是该合约的一个操作版本,只要在CHenry中实现了接口IHenry定义的所有的方法,不管是怎么实现的,有没有加入新的方法,都可认为CHenry是支持IHenry接口的一个实现类。”
“一个实现类?也就是说接口可以有多个实现喽?”我不解地问。
“当然,我刚才说过,接口其实就是一个对象的外观,在VB.NET中有很多很重要的接口,定义了很多种类型的对象,比如说你所熟悉的Windows Form的控件,它们的基类大部分是Component类,而Component类就是IComponent接口的一个实现类,IComponent类还有其它三个实现类,那就是:Control、HttpApplication和MarshalByValueComponent类,分别完成不同的功能,但它们都要实现IComponent接口所定义的方法与属性,且参数定义与返回类型都要与接口定义时的签名一致。换个角度来看这个问题,我们如何创建一个组件,且让系统都识别,靠的就是对接口的实现。要成为组件的类必须实现 IComponent 接口,并提供不需要参数或只需一个类型为 IContainer 的参数的基本构造函数就行了。”
“哦,我明白了,通过接口,我们可以定义下某种对象的基本外观,然后可以自由地进行实现与扩展,却不涉及对原型的直接修改。太棒了!”我一下子高兴了起来。
“是呀,在VB.NET中可以显式定义接口,使得接口编程也成为很棒的编程方式了。刚才所说的,在不同的类中实现同一个接口,不就是一种用接口实现的多态性吗?另外,在类和结构中也可以实现多个接口。”大李写了如下的代码给我看:
Interface IOne Sub SubOne() End Interface Interface ITwo Sub SubTwo() End Interface Class CHenry Implements IOne Implements ITwo Sub SubOne() Implements IOne.SubOne '实现代码 End Sub Sub SubTwo() Implements ITwo.SubTwo '实现代码 End Sub End Class |
“真有意思,”我饶有兴致地看着代码,“也就是说CHenry就具备了IOne与ITwo所定义的外观特点了。”
我一下子想起了抽象类的定义,不由好奇地说:“如果把接口看成一个基类,那么用于实现的类不就可以看成是它的一个派生类了?这是不是就是接口继承呀?”