使用属性和集合创建对象模型

分层结构中的对象通过对象属性链接在一起,即对象属性是返回对象引用的属性。包含其它对象的对象的属性可以返回对这些对象的引用,或者对对象集合的引用。

例如,考虑包含两个 Wheel 对象的 Bicycle 对象;每个 Wheel 对象都拥有一个 Rim 对象和一个 Spoke 对象集合。图 6.6 显示了外部可创建的 Bicycle 对象及其从属对象的一种可能的对象模型。

图 6.6   带有从属对象分层结构的外部可创建对象

该 Bicycle 对象具有 Frame 属性来返回对其 Frame 对象的引用。而 Frame 对象有 FrontWheel 和 BackWheel 属性,分别返回一个 Wheel 对象。Wheel 对象有 Spokes 属性,返回 Spokes 集合对象的引用。Spokes 集合包含 Spoke 对象。

可能会有只想在部件的类内部使用的从属对象,而不想提供给部件的用户。将定义这些对象的类的 Instancing 属性设为 Private,则它们在用户浏览类型库时不会出现在其中。

例如,Frame 和 Wheel 对象都可以有 Bearing 对象的集合,但没有必要显露该 Bearings 对象或包含该对象的集合,而使用户端应用程序能操作它。

重点   为了使 Bearings 属性不出现在类型库中,必须按本章前面“对象之间的私有通信”所讲述的,使用 Friend 关键字声明该属性。

链接从属对象的简便方法

使复杂对象只有一个从属对象的实例是明智的。例如,Bicycle 对象就只需要一个 Frame 对象。在这种情况下,只需将连接作为复杂对象的一个简单属性实现即可。

Private mFrame As Frame

Public Property Get Frame() As Frame
   Set Frame = mFrame
End Property

Private Sub Class_Initialize()
   'Bicycle 初始化时创建 FrameSet mFrame = New Frame
End Sub

按上述方法使用只读属性过程实现这些属性是很重要的,而不要象下面的语句那样只简单地定义一个公共模块级变量。

Public Frame As Frame      '不是好做法。

在第二种实现方案中,该部件的用户可能将 Frame 属性设为 Nothing。如果没有其它对该 Frame 对象的引用,它将被破坏。至于这样导致的对 Bicycle 对象的影响读者可以自己相象。

链接固定数量的对象

即使当复杂对象包含不止一个的从属对象的实例时,用属性而不用集合来实现链接也是明智的。例如,一个 Bicycle 对象总是有两个轮子:

'创建所需 Wheel 对象 (As New),替代 Bicycle 对象的初始化事件。
Private mwhlFront As New Wheel
Private mwhlRear As New Wheel

Public Property Get FrontWheel() As Wheel
   Set FrontWheel = mwhlFront
End Property

Public Property Get RearWheel() As Wheel
   Set RearWheel = mwhlRear
End Property

在对象模型中使用集合

如果分层结构中的两个对象的关系是一个对象包含不确定数目的另一个对象,则实现链接的最容易的方法就是使用集合。所谓集合就是包含一组相关对象的对象。

例如,在图 6.6 中的 FrontWheel 对象及其 Spoke 对象之间的链接就是集合类。所谓集合类就是为将其它某个类的对象归在一起而专门设计的类模块。在这种情况下,该集合类就称 Spokes,也即它所包含对象的类名的复数形式(关于类的命名的更详细的信息,请参阅本章前面的“命名的学问”)。

这种对象模型的示例的实现使用了三个类模块,从底层往上分别是:

从属类:Spoke

Spoke 类模块最简单。它可以少到仅由两个 Public 变量组成,如下面的代码段所示:

'Spoke 的属性
Public PartNumber As Integer
Public Alloy As Integer

Spoke 类的 Instancing 属性设为 PublicNotCreatable。客户端应用程序创建 Spoke 对象的唯一方法就是使用下一节介绍的 Spokes 集合类所带的 Add 方法。

注意   这并不是一个非常强健的实现方案。实际上可能会把这两个类的属性作为 Property 过程实现,通过代码来验证赋给它们的值。

详细信息   关于使用 Property 过程的更详细论述,请参阅《Visual Basic 程序员指南》的“用对象编程”。

从属集合类:Spokes

Spokes 类模块是 Spoke 对象集合的模板。它包含一个定义为 Collection 对象的 Private 变量。

Private mcolSpokes As Collection

该集合对象在其所属类的初始化方法中创建:

Private Sub Class_Initialize()
   Set mcolSpokes = New Collection
End Sub

Spokes 类模块的方法沿用 Visual Basic Collection 对象的缺省方法。也就是说,真正的工作是由 Collection 对象的方法完成的。Spokes 类可能包含下述属性和方法:

'只读 Count 属性。
Public Property Get Count() As Integer
   Count = mcolSpokes.Count
End Property

'创建新 Spoke 对象的 Add 方法。
Public Function Add(ByVal PartNumber As Integer, _
               ByVal Alloy As Integer)
   Dim spkNew As New Spoke
   spkNew.PartNumber = PartNumber
   spkNew.Alloy = Alloy
   mcolSpokes.Add spkNew
   Set Add = spkNew
End Function

与 Spoke 类一样,Spokes 类的 Instancing 属性也设为 PublicNotCreatable。获得 Spokes 集合对象的唯一方法是将其作为 Wheel 对象的一部分,请参见下一节对 Wheel 对象的介绍。

详细信息   请参阅《Visual Basic 程序员指南》的“用对象编程”的“对象模型”关于集合的论述,其中包括对沿用的更详细阐述,需要实现的方法列表以及如何创建使用 For Each 工作的集合。

外部可创建的对象:Wheel

Wheel 类模块将 Instancing 属性设为 MultiUse,所以任何客户端应用程序都可以创建 Wheel 对象。Wheel 类模块包含一个 Spokes 类的 Private 变量:

'创建所需 Spokes 集合对象。
Private mSpokes As New Spokes

Public Property Get Spokes() As Spokes
   Set Spokes = mSpokes
End Property

客户端创建的每个 Wheel 对象都具有自己的 Spokes 集合。该集合可以设置为只读属性 (Property Get) 以防偶然被设为 Nothing。可以用如下所示的代码段访问 Spokes 集合的属性和方法:

Dim whl As Wheel
Dim spk As Spoke
Set whl = New Wheel
Set spk = whl.Spokes.Add PartNumber:=3222223, Alloy:=7
'调用 Spoke 对象的一个方法。
spk.Adjust
MsgBox whl.Spokes.Count      '显示 1(一项)。

Add 方法用于创建 Wheel 对象的 Spokes 集合的新的 spoke。该方法返回一个对这个新的 spoke 对象的引用,从而使其属性和方法可被调用。spoke 只能作为 Spokes 集合的成员被创建。

由任何客户端创建的 Wheel 对象与其从属对象之间的区别就在于各自类中的 Instancing 属性值不同。

详细信息   Collection 对象 mcolSpokes 被显式创建,而Spokes 对象则在请求时即可创建 Spokes 对象。《Visual Basic 程序员指南》的“用对象编程”论述了使用 As New 创建所需变量的方法,以及对性能的说明。

在对象模型中链接对象的注意事项

一般来说,简单的实现方案运行起来快。对集合中数据项的访问会引发一系列的嵌套引用和函数调用。只要从属对象类型的数目固定,就可以将这种连接用属性来实现。

无论对象模型如何连接,外部可创建的对象与从属对象之间最关键的区别就是类模块的 Instancing 属性的取值。可由其它应用程序创建的对象,其 Instancing 属性设为除 Private 或 PublicNotCreatable 之外的任何值。

所有的从属对象,不管是包含在其它从属对象中还是包含在可被其它应用程序创建的对象中,其 Instancing 属性均应设为 PublicNotCreatable。

外部可创建的对象作为从属对象使用

有时,可能想要用两种方式使用对象。例如,既允许用户创建一个独立于对象模型 Widget 对象,同时又要提供一个 Widgets 集合作为 Mechanism 对象的属性。

实际上,有时甚至允许用户创建 Widgets 集合的独立实例、允许将独立的 Widgets 移进或移出任意的 Widgets 集合、以及允许在集合间复制或移动 Widgets。

可以把 Widget 类和 Widgets 集合类的 Instancing 属性设为 MultiUse,使该对象成为外部可创建的对象。

重点   如果 Widget 对象可被客户端应用程序直接创建,就不能依靠 Widgets 集合的 Add 方法的代码来初始化所有的 Widget 对象。既可在外部创建又是从属对象的对象应设计为只需在其初始化事件中进行初始化。

要允许 Widgets 自由移动,则需要实现集合的 Insert、Copy 和 Move 方法。Insert 和 Move 较为简单,因为对象引用的移动和插入与对象的移动没什么不同。Copy 的实现则比较困难。

这是因为事实上对象并不在客户端应用程序的进程中。所有的客户端应用程序,为了自身的方便,都只具有部件所创建对象的引用。因此,为实现 Copy,必须创建一个对象副本,其中包括该对象中所有的从属对象的副本。

详细信息   请参阅“循环引用的处理”中关于对象连接在一起时可能产生的问题的论述。关于对象模型的更详细的信息,请参阅“ActiveX 部件的标准及指南”。