大李拍了拍我的肩膀说:“你真有想象力,不过的确,有很多文献把这种用Implements来实现接口的方法就称为接口继承。其实,接口自己也是可以进行继承的,在VB.NET中把接口间的继承形式称为接口继承。”@H_403_2@
我不禁跟着笑了起来:“接口继承要成为继承,当然要用Inherits,对吧?”@H_403_2@
大李点点头说:“既然你都清楚了,那你来模拟一个下拉框ComboBox的接口吧。”@H_403_2@
“ComboBox?”我不禁一愣,不过一会就想明白了,“是不是要让它符合有文本框供文字输入与下拉列表供选择列表项的组合形式这样的外观?”@H_403_2@
大李跟着提醒了我一句:“接口与VB.NET中的类继承还是有不同的,它可以支持从多个接口进行多重继承,VB.NET中的类只支持单一基类的继承。”@H_403_2@
见大李没什么别的意见,我就开始写起代码来:@H_403_2@
Interface IControl@H_403_2@ Sub Paint()@H_403_2@ End Interface@H_403_2@ Inherits IControl@H_403_2@ ‘在文本框设置文本@H_403_2@ Sub SetText(ByVal text As String)@H_403_2@ End Interface@H_403_2@ Inherits IControl@H_403_2@ ‘在下拉列表设置列表项@H_403_2@ Sub SetItems(ByVal items() As String)@H_403_2@ End Interface@H_403_2@ Inherits ITextBox,IListBox@H_403_2@ End Interface@H_403_2@ Class CHenry@H_403_2@ Sub SetText(ByVal text As String) Implements ITextBox.SetText@H_403_2@ End Sub@H_403_2@ Sub SetItems(ByVal items() As String) Implements IListBox.SetItems@H_403_2@ End Sub@H_403_2@ ……@H_403_2@ |
写到这,发现CHenry类中的Implements IcomboBox的IComboBox下面还有一条波浪线,说明接口并没有实现完毕,可是我已经把IComboBox继承的两个基接口中的方法都已经实现了呀。把鼠标靠近波浪线一看,系统提示“必须为接口IControl实现sub Paint()”,于是我就继续写:@H_403_2@
Sub Paint() Implements IControl.Paint@H_403_2@ End Sub@H_403_2@ End Class@H_403_2@ |
我转回头问大李:“接口的实现类中是不是要把接口的所有基接口都要实现一遍呀?”@H_403_2@
大李点点头说:“如果象这个演练中的情况,当然是要把基接口中没有实现的方法进行实现。但也要注意,实现接口的类或结构会隐式地实现该接口的所有基接口。如果一个接口在基接口的可传递闭包中多次出现,它的成员只参与一次构成派生接口。实现派生接口的类型只需实现一次多次定义的基接口方法。所以你也可以用Sub Paint() Implements ITextBox.Paint或是Sub Paint() Implements IListBox.Paint来代替,但只能用这三个定义中的一个。你再来看这段代码。”大李开始修改起刚写好的代码来:@H_403_2@
Interface IControl@H_403_2@ Sub Paint()@H_403_2@ End Interface@H_403_2@ Inherits IControl@H_403_2@ ‘在文本框设置文本@H_403_2@ Sub SetText(ByVal text As String)@H_403_2@ Shadows Sub Paint()@H_403_2@ End Interface@H_403_2@ Inherits IControl@H_403_2@ ‘在下拉列表设置列表项@H_403_2@ Sub SetItems(ByVal items() As String)@H_403_2@ End Interface@H_403_2@ Inherits ITextBox,IListBox@H_403_2@ End Interface@H_403_2@ Sub test(ByVal x As IComboBox)@H_403_2@ x.Paint()@H_403_2@ End Sub@H_403_2@ |
“这里的x.Paint()是哪一个接口的方法?IControl是ITextBox?”大李一脸笑意,真是气人。但是,我应该可以回答上来的,我按类的隐藏的概念回忆了一下(详见前文《重载和隐藏》),哈,明白了,它当然是调用它直接被派生的那个基类中的方法呀。@H_403_2@
“可以呀,不错!”大李简单地夸了我一句,然后喝了口水,继续说:“基接口的成员名称在继承分层结构的一条路径中被隐藏,但它在其它的路径中不会被隐藏,比如我们可以从IlistBox中去继承Icontrol中的Sub Paint()。”@H_403_2@
“可是,在您的这个示例中的sub test里,x是接口的实例吗?可是,接口还没有实现呀?”我还是有问题要问。@H_403_2@
“test方法其实可以接受任何将 IComboBox 实现为小部件参数的对象,即使对接口 IComboBox 的实现可能相差很大。”大李回答道。@H_403_2@
“是不是说我们在使用的时候,可以用实现IComboBox接口的类,比如CHenry的一个实例去代替x?”@H_403_2@
大李笑着说:“基本上差不多了,你自己慢慢考虑吧。还有个问题比较有意思:实现类中用于实现接口的方法或属性名倒不用与接口中定义的名字一样,只要参数列表与返回类型一致就行了。比如在CHenry中的sub Paint()如果更名为sub xxx()也是可以的,只要后面跟着Implements IControl.Paint就行了。命名一定要有规划,不然,接口继承中也会带来命名重复造成的问题,我们来看一下。”@H_403_2@
Interface IHenry1@H_403_2@ Property yyy() As Integer@H_403_2@ End Interface@H_403_2@ Interface IHenry2@H_403_2@ Sub yyy(ByVal i As Integer)@H_403_2@ End Interface@H_403_2@ Interface IHenryDerived@H_403_2@ Inherits IHenry1@H_403_2@ Inherits IHenry2@H_403_2@ End Interface@H_403_2@ Sub test(ByVal x As IHenryDerived)@H_403_2@ x.yyy(1)@H_403_2@ x.yyy = 10@H_403_2@ End Sub@H_403_2@ |
“你看,在sub test()中,无论你按IHenry2中的定义方式来使用x.yyy(1),还是用IHenry1中的方式来使用x.yyy=10,集成编译器都会在它们下方打上波浪线,表示出错,是什么错呢?”大李一边问我,一边把鼠标靠近波浪线,出现了编译器的出错提示:@H_403_2@
“yyy”在继承接口“IHenry1”与“IHenry2”之间不明确@H_403_2@
“所以,我一直强调命名规则,对吧?”大李看了我一眼,“其实解决方法倒用不着去更改基接口中的方法与属性名。”@H_403_2@
Sub test()@H_403_2@ Dim x As IHenryDerived@H_403_2@ CType(x,IHenry1).yyy = 10@H_403_2@ CType(x,IHenry2).yyy(1)@H_403_2@ End Sub@H_403_2@ |
“哦,用强制类型转换就可以了。”我又学到一招,不禁暗自窃喜。但是我心里总是有一个不大不小的疙瘩,说来说去,这接口与抽象类可真的太象了。赶紧得问问:“大李哥,这接口与……”@H_403_2@
“抽象类?”大李一口就接了上来:“别急,小伙子,看看几点了,该下楼吃午饭了。”@H_403_2@