VB不能继承,于是就会用一个接口来绕个弯路,达到继承的效果。
接口: Implements
接口并没有实际的代码,是一个“空壳”,有点象虚拟类(不存在的类的)
接口相当于是一个模板,一个空壳的方法和属性。也可以说是抽象的方法和抽象的属性。接口通过类模板定义,又称接口类
其它类通过接口(这个公共的模板)来把“空壳”来完善,实现其中的数据或方法。
先熟悉一个例子,认识一下:
从CShape类(空壳类,作为接口),产生两个类CPoint和CCircle,每个类都是各自的一个类模块:
1、空壳类:Cshape
Option Explicit '*********************************** '这是一个形状接口 '*********************************** Public Function Area() As Double '面积,空壳,无代码 End Function Public Function Name() As String '名称,空壳,无代码 End Function Public Function ToString() As String '输出,空壳,无代码 End Function
2、CPoint类(注意接口声明)
Option Explicit Implements IShape '接口声明,I前缀开始表明接口定义 Private mX As Integer Private mY As Integer Private Function IShape_Area() As Double IShape_Area = 0 End Function Private Function IShape_Name() As String IShape_Name = "Point" End Function Private Function IShape_ToString() As String IShape_ToString = "[" & mX & "," & mY & "]" End Function Public Property Let X(ByVal newX As Integer) mX = newX End Property Public Property Get X() As Integer X = mX End Property Public Property Let Y(ByVal newY As Integer) mY = newY End Property Public Property Get Y() As Integer Y = mY End Property
3、CCircle类(注意接口,都是IShape)
Option Explicit Implements IShape Private mX As Integer '不是从CPoint中继承 Private mY As Integer Private mRadius As Double Private Function IShape_Area() As Double IShape_Area = 3.14159 * mRadius ^ 2 End Function Private Function IShape_Name() As String IShape_Name = "Circle" End Function Private Function IShape_ToString() As String IShape_ToString = "[" & mX & "," & mY & "," & mRadius & "]" End Function Public Property Let X(ByVal newX As Integer) mX = newX End Property Public Property Get X() As Integer X = mX End Property Public Property Let Y(ByVal newY As Integer) mY = newY End Property Public Property Get Y() As Integer Y = mY End Property Public Property Let Radius(ByVal newR As Double) mRadius = newR End Property Public Property Get Radius() As Double Radius = mRadius End Property
这样就把三个类定义好了,接口类IShape什么代码都没有。CPoint和CCircle都来自于IShape
下面看一下,它是怎么达到“继承”的效果。
窗体模块(标准模块)中:
Option Explicit Private Sub Command1_Click() Dim p As New CPoint Dim c As New CCircle Dim iRef As IShape '创建引用 p.X = 500 p.Y = 777 Set iRef = p '引用连接到CPoint类上 Print iRef.Name & " Area:" & iRef.Area & vbCrLf & iRef.ToString c.Radius = 4 c.X = 11 c.Y = 812 Set iRef = c Print iRef.Name & " Area:" & iRef.Area & vbCrLf & iRef.ToString End Sub
看完了例子,感觉有点多余,直接使用相对就的对象(CPoint或CCircle)不就完了?
是的,可以这样做。但是。。。
很多情况,在调用一个对象时我们并不清楚这个对象是哪一个,也许是CPoint,也许是CCircle,
这种情况无法猜测是哪一个对象,也就无法手动指定它是哪一个对象,那我们更无法来确定成员了。怎么办?
这时,就可以巧妙地用一个对象CShape来代替,它就可以直接由Cpoint或CCircle来赋值。换到使用的场景时就可直接使用CShape了。
这就是 多态性。
简单的一句话就是:将父对象设置成为和一个或多个子对象相等的技术,就是多态。
表现形式即: Set Parent= New Child
上面可以看接口的应用。但没理解其中的本质。
下面是别人的一个例子,但却很好地说明了内部的实质。
在看这个例子前熟悉几个概念:
指针:指向一个变量的变量,存储的是被指向变量的地址。
地址:每个对象或变量都在内存中存储,存储的内存位置都有一个编号,这个编号就是地址,计算内部都是根据编号来找变量或地址的。
地址的求法,VB中有三个常用的:(注意,地址是4个字节的长整形)
ValPtr value pointer 即变量的地址
StrPtr string pointer 即字串的地址
ObjPtr object pointer 即对象的地址
CopyMemory把一块内存中的东西复制到另一块中去。
下面看一下这人例子,由接口类“人”,“继承”出两个类“大人”,“小孩”,然后再仔细看一下在标准模块中的应用
1、接口类:人
Option Explicit Public Name As String '属性 Public Sub Eat() '没有内容,空壳 End Sub Public Sub birth() '没有内容,空壳 End Sub
2、“继承”类:大人
Option Explicit Implements 人 '接口声明 Dim 大人Name As String Private Sub 人_Eat() MsgBox "手艺不错" End Sub Private Sub 人_Birth() '空着(大人不可能出生)但必须要有这个过程 End Sub Public Sub work() MsgBox "tired" End Sub Private Property Let 人_Name(ByVal RHS As String) '继承属性 大人Name = RHS End Property Private Property Get 人_Name() As String 人_Name = 大人Name End Property
3、“继承”类:小孩
Option Explicit Implements 人 '实现接口 Dim 小孩Name As String Private Sub 人_Eat() MsgBox "妈妈喂" End Sub Private Sub 人_Birth() MsgBox "哇…哇…" End Sub Public Sub play() MsgBox "ha ha!" End Sub Private Property Let 人_Name(ByVal RHS As String) '继承属性 小孩Name = RHS End Property Private Property Get 人_Name() As String 人_Name = 小孩Name End Property
4、上面声明完后,开始应用
Option Explicit Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any,Source As Any,ByVal Length As Long) Dim xh As New 小孩 Dim dr As New 大人 Dim r As New 人 Dim TheObjPtr As Long Dim TmpOBJ As 人 '声明类型,作引用,注意没有New Private Sub Form_Load() xh.play '没有作为接口类使用时 dr.work r.Eat ' TmpOBJ=Nothing ' 方法一: ' SetRealObj xh '注意了,xh为小孩类,但由于接口,可以符合OBJ As 人 ' TmpObjEat '方法二: Set TmpOBJ = dr '这里也是可以符合,使TmpPBJ设置成大人 TmpOBJ.Eat TmpOBJ.Name = "小明" '属性的继承 MsgBox TmpOBJ.Name Set TmpOBJ = Nothing '释放对象 End Sub Function SetRealObj(OBJ As 人) TheObjPtr = ObjPtr(OBJ) End Function Sub TmpObjEat() Dim BakPtr As Long CopyMemory BakPtr,TmpOBJ,4 '备份TmpOBJ的内存地址 CopyMemory TmpOBJ,TheObjPtr,4 '使TmpPBJ“直接变成”小孩 TmpOBJ.Eat CopyMemory TmpOBJ,BakPtr,4 '因为改动内存地址,结束时会出错,必须恢复,而下面Set TmpOBJ = Nothing则不是必须的 End Sub
分析:
上面用的方法二,实质上和第一个例子没有区别。
看一下方法一,前面的TmpOBJ也需注释掉,因为本身就是空的,后面再来释放会出错
方法一中,第一句是把小孩类的对象xh的地址送给TheObjPtr,为下面一句的过程作准备。
下面过程TempObjEat()过程,第一句定义一个临时长整形,用来存放(TempOBJ地址,以便最后恢复TmpOBJ的地址)
第二句,提出TmpOBJ的地址存储到BackPtr中
第三句,把“继承”类对象的地址TheObjPtr,放到超类TempOBJ中,
第四句,用超类来显示值(实际是xh的值)
第五句,还原。
上面可以看到,继承的本质东西在里面了,第三句因为“继承”,同类的指针可以复制过来,并在第四句中得以正确的显示。
总之一句:父类引用指向子类。
理解上面后,再看接口的注意点:
1、超类的定义过程需是一个空壳
而且后面要被继承的成员(过程或数据)必须是Public,才能通过它来引用后面继承对象的成员(因为后面都要通过这个公共接口去访问)
一般把继承类中对应的成员设置为private(如果是过程就用function,数据用property)
2、子类的定义过程(重复超类的定义)需是以超类名开头,加上下划线,再是同名,比如:
IShape_ToString 有三部分:第一部分IShape,空壳类名不能变;第二:下划线不能省略;第三:名称ToString不能变化。
对于超类没有的可以自己定义。
MSDN上面关于implements也有一例子。
首先form2里面定义了一个属性PD,它存储的是父类(接口类)的对象。为下一步向里面存储继承的对象作准备。
然后form1里,两个按键,根据不同按键存储分别存储不同的子类对象。
这样按不同的按键时,接口类就会根据指向的对象,以多态的方式,指向对应不同的对象,尽管看起来都使用同一个对象,但它们指向不同。