多态性、接口、类型库和 GUID

部件提供一些类,客户端可以通过创建该类的对象来获得部件提供的服务。客户端通过创建对象以及调用其属性和方法来使用这些服务。

关于部件所提供的类的信息包含在类型库中。Visual Basic 的类型库是编译好的部件的资源。客户端引用该类型库就可以对其进行访问。

设置类型库名称

位于“工程属性”对话框的“通用”选项卡的“工程名称”设置了部件的类型库名,并限定类的名称。例如,下面的代码段定义一个引用 Widget 类的对象的变量,该类由一个工程名为 SmallMechanicals 的部件提供。

Public gwdgDriveLink As SmallMechanicals.Widget

有些应用程序可以操作对象,但是不能声明特定对象类型的变量。这样的应用程序声明通用的对象变量为 As Object,并用 CreateObject 函数中的 projectname 参数中的工程名获得对新的对象的引用,如下所示:

Set objectvariable = CreateObject(projectname.class)

工程名和类名的组合称为完全限定的类名,或称为程序 ID。要正确地标识属于某个部件的对象就需要用到完全限定的类名。例如,可以在部件中实现一个 Window 类。Microsoft Excel 也提供了一个 Window 对象,这样就可能在客户端应用程序中出现如下的混乱:

'Microsoft Excel Window 类的变量。
Dim xlWindow As Excel.Window 
'ProgramX 部件的 Window 类的变量。
Dim pxWindow As ProgramX.Window
'属于 Microsoft Excel  ProgramX 部件的 Window 类变量,
'在客户端应用程序的“引用”对话框中首先出现!
Dim xWindow As Window

缺省接口

所谓接口就是一些属性、方法和事件的集合。部件所提供的每个类都至少拥有一个称之为缺省接口的接口,该接口包括该类模块中定义的所有属性和方法。

尽管缺省接口的名称是类名前之加下划线,但通常使用类名引用缺省接口。下划线前缀是一种约定,表明该名字隐藏在类型库中。

如果类产生事件,则有一个列举所有事件的 IConnectionPointContainer 接口。事件是调出接口,与之相对的是由属性和方法组成的调入接口。换句话说,客户端使用类内部的属性和方法产生请求,而类产生的事件则调用外部的客户端的事件处理程序。

调入和调出的接口在接口示意图中用不同的符号表示,如图 6.1 所示。

图 6.1 调入和调出接口

重点 要把接口视为部件的开发人员和使用人员之间的协议,接口的改变可能导致使用该部件编译的应用程序失效。

Visual Basic 提供两种途径使部件的升级不会对已编译的应用程序产生影响:对接口版本的兼容性以及多接口技术。为详细介绍这些方法,先来学习一下 GUID。

类型库、接口和 GUID

GUID(读音为 goo-id)即全局唯一标识符,是由确保唯一性的算法生成的一个 128 位(16 字节)的数。该算法来源于开放软件基金会 (OSF) 的分布式计算环境 (DCE),是关于分布式计算的一套标准。

GUIDs 通常用于唯一地标识 Windows 注册表的入口。例如,Visual Basic 自动生成一个 GUID 来在 Windows 注册表中标识类型库。

Visual Basic 也自动为部件的每一个公共类和接口生成一个 GUID。这些通常称之为 ID (CLSID) 和接口 ID (IID)。类 ID 和接口 ID 对使用 Visual Basic 开发的部件的版本兼容性是很关键的。

注意   GUIDs 也称为 UUID,即通用唯一标识符。

Visual Basic 不会耗尽 GUID 吗?

不必担忧这个问题。生成 GUID 的算法可以保证在几个世纪的每一秒钟内编译部件的数个新版本─ 也不会与其它开发者的 GUID 重复或冲突。

接口的版本兼容性

当编译使用了某部件的程序时,该程序所创建的所有对象的类 ID 和接口 ID 都包含在可执行程序中。

程序使用类 ID 请求部件创建对象,然后查询该对象的接口 ID。如果接口 ID 不存在则会发生错误。

只要在“工程属性”对话框的“部件”选项卡中选择了“工程兼容”或“不兼容”,则在新部件的开发期间,Visual Basic 在每次编译时都会生成新的 CLSID 和 IID。部件发布后,当开始进行部件升级版本的工作时,则可以选择 Visual Basic 的“二进制版本兼容性”来改变这种行为。

如在“调试、测试和部署部件”一章的“版本兼容性”中的详细论述,二进制版本兼容性继承部件先前的版本的类 ID 和接口 ID。这样,用原版本编译的应用程序在新版本下也能工作。

为确保兼容性,Visual Basic 对改动缺省接口有若干限制。Visual Basic 允许添加新类,以及给已有的类添加属性和方法来增强缺省接口。删除类、属性或方法,以及改动已有类的属性或方法的参数都会导致 Visual Basic 发出不兼容的警告。

如果要使用不兼容的接口,Visual Basic 会改变类型库的主版本号,并建议改变执行程序名和工程名,以保证该部件的新版本不会覆盖用户硬盘上的旧版本。

多接口:多态性与兼容性

在《Visual Basic 程序员指南》“用对象编程”的“多态性”中介绍的 Implements 语句,允许实现类的附加接口。

如果两个类实现了相同的辅助接口,就这个接口而言这两个类就是多态的。也就是说,客户端对接口的属性和方法可以产生事前连结调用,而无须关注对象所使用的类。

如果创建标准的接口并在多个类中实现,这些类由一个或多个部件提供,就可以在应用程序或整个系统中获得多态性的优点。

详细信息 本章后面的“用 Visual Basic 创建标准接口”解释如何通过定义不包含实现代码的类模块来创建接口。

接口与兼容性

多接口提供了另外一种方式,既保持与使用较早版本部件编译生成的旧应用程序兼容,又能对部件进行升级和扩展。

要确保多接口的兼容性,必须遵守下面的 ActiveX 规则:一旦接口投入使用,就不能再改动。标准接口的接口 ID 由定义该接口的类型库确定。

增强标准接口的途径是创建一个新的标准接口,以补充增强功能。未来的部件或已有部件的未来版本既可以实现旧的接口,也可实现新的接口,或者两种接口都实现,如图 6.2 所示。

图 6.2 多接口的兼容方法

IFinance 和 IFinance2 接口在各自的类型库中定义,并由实现接口的部件(在本例中,即 Finance.dll 的 1.0 版和 2.0 版)和使用接口的应用程序(Payroll 和 GeneralLedger)引用。

由于未来的应用程序可以获得新接口的优点,因而系统可以不断发展。只要新的部件在实现新接口时仍旧实现旧的接口,已有的应用程序就可以在新部件下继续工作。

使用部件多个版本的应用程序

应用程序可以使用部件的任意的版本。例如,如果可用 IFinance2 接口,GeneralLedger.exe 的 2.0 版就可以使用 IFinance2 接口,否则就使用 IFinance 接口。

显然,在后一种情形下,GeneralLedger 只能提供有限的功能集。这种由于缺乏最佳接口造成的只能提供有限功能的现象称之为得体的降格

为了能在每一个接口下工作,GeneralLedger 要包含如下代码:

Dim fnr As FinanceRules
Dim ifin As IFinance
Dim ifin2 As IFinance2
   
On Error Resume Next
Set fnr = New FinanceRules
'(忽略错误处理程序代码。)
'试图访问最佳接口。
Set ifin2 = fnr
If Err.Number <> 0 Then
   '访问限制更多的接口。
   Set ifin = fnr
   '(使用对象变量 ifin 提供有限功能的代码。)
Else
   '(使用对象变量 ifin2 提供完整功能的代码。)
End If

详细信息 本章后面的“通过接口的实现来提供多态性”详述了标准接口的使用和命名方法。