私有集合示例:树枝盖的房子

在本主题中,继续讨论在上面的“公有集合示例:稻草盖的房子”中开始探讨的代码示例。在开始本主题之前,最好阅读上面的主题。

将 Employee 对象和 SmallBusiness 对象连接起来的一种更强健一些的办法是使 Collection 对象私有化。对于该示例来说,将要从上面的“公有集合”示例中重用窗体和大部分代码。

Employee 类模块没有改变。然而,SmallBusiness 类模块被完全翻新了。用下面的声明语句来取代公有的 Collection 对象的声明,并把下面几个段落中所描述的 Sub 和 Function 过程添加进去。

Option Explicit
Private mcolEmployees As New Collection

跟前面的一样,添加一个雇员的代码做大部分工作。(可以从前面示例中的 cmdEmployeeAdd_Click 事件过程中把点线之间的代码块取出来。)

重要的改动在于:Collection 对象的 Add 方法不能再从程序的任意模块来调用了,因为 mcolEmployees 是 Private。只能用 EmployeeAdd 方法来添加 Employee 对象,这将会使新对象正确地初始化:

'SmallBusiness 的类方法。
Public Function EmployeeAdd(ByVal Name As String, _
ByVal Salary As Double) As Employee
   ' - - - - - - - - - - - - - - - -
   Dim empNew As New Employee
   Static intEmpNum As Integer
   ' With 语句,使代码更快,也更简洁
   '.ID 对应 empNew.ID)With empNew
      '为新雇员产生一个唯一的 IDintEmpNum = intEmpNum + 1
      .ID = "E" & Format$(intEmpNum, "00000")
      .Name = Name
      .Salary = Salary
      ' Employee 对象引用添加到集合中,
      ' ID 属性作为键。
      ' - - - - - - - - - - - - - - - -
      mcolEmployees.Add empNew, .ID
   End With
   '给新雇员返回一个引用。
   Set EmployeeAdd = empNew
End Function

EmployeeAdd 方法为新添加的 Employee 对象返回一个引用。这是一个好的实践,因为一旦创建了一个对象,很可能就想用它来做点什么事。

EmployeeCount、EmployeeDelete 以及 Employees 方法委派 Collection 对象的相应方法为代表。委派意味着 Collection 对象做全部工作。

'SmallBusiness 类的方法。
Public Function EmployeeCount() As Long
   EmployeeCount = mcolEmployees.Count
End Function

Public Sub EmployeeDelete(ByVal Index As Variant)
   mcolEmployees.Remove Index
End Sub

Public Function Employees(ByVal Index As Variant) _
As Employee
   Set Employees = mcolEmployees.Item(Index)
End Function

注意   可以将附加的功能添加到这些方法中。例如,如果一条索引项是无效的,能够引发自己的错误。

最后的方法是 Trouble。该方法试图将未初始化的 Employee 对象添加到集合中。能猜测到将会发生什么事吗?

'SmallBusiness 类的方法。
Public Sub Trouble()
   Dim x As New Employee
   mcolEmployees.Add x
End Sub

对窗体的更改

现在,将不得不对窗体模块作一些改动。可以使用前面示例所使用的同样的模块级声明,而且“关闭”按钮的 Click 事件也是相同的,但是,其它事件过程已经改变了—“添加”按钮的代码短了许多,而“删除”和“列出雇员的表”按钮的代码则已经改变为一种很小但却很有效的方式:

Private Sub cmdEmployeeAdd_Click()
   sbMain.EmployeeAdd txtName.Text, txtSalary.Text
   txtName.Text = ""
   txtSalary.Text = ""
   cmdListEmployees.Value = True
End Sub

Private Sub cmdEmployeeDelete_Click()
   '检查一下,以确保有雇员被选中。
   If lstEmployees.ListIndex > -1 Then
      '前六个字符是 IDsbMain.EmployeeDelete Left(lstEmployees.Text, 6)
   End If
   cmdListEmployees.Value = True
End Sub

Private Sub cmdListEmployees_Click()
   Dim lngCt As Long
   lstEmployees.Clear
   For lngCt = 1 To sbMain.EmployeeCount
      With sbMain.Employees(lngCt)
         lstEmployees.AddItem .ID & ", " & .Name _
         & ", " & .Salary
      End With
   Next
End Sub

但是,在 cmdListEmployees_Click 中的所有这些附加代码是什么?不幸的是,在追求强健性的过程中,就已经放弃了使用 For Each ... Next 来遍历集合中各项的能力,因为现在 Collection 对象已经声明为 Private。如果试图象下面这样编写代码,那么将会产生错误:

'不会正常工作,因为 Employees 不是一个真正的集合。
For Each emp In sbMain.Employees

幸运的是,可以用 EmployeeCount 方法来为遍历范围分界。

“有问题”按钮也作了少许更改,但它仍然是“有问题”。

Private Sub cmdTrouble_Click()
   sbMain.Trouble
End Sub

运行该工程,并用“添加”、“删除”以及“刷新表”按钮作一下试验。每件事情工作起来,都跟以前是一样的。

当单击“有问题”按钮时,还是不产生任何错误。但是,如果现在单击“刷新表”按钮的话,就可以看到未初始化的 Employee 对象已经以某种方式被添加到集合中了。

怎么会发生这种情况?使 Collection 对象私有化以后,就保护它不受程序中所有在 SmallBusiness 对象外部的代码的影响了,但并没有防范内部代码的影响。在这种情况下,SmallBusiness 对象就可能由于内部有大量代码而变得很大,而且很复杂。例如,它将很可能具有象 CustomerAdd、ProductAdd,等等这些方法。

代码错误,或者 EmployeeAdd 方法的重复创建,仍然可能造成错误数据—甚至无效的对象—被插入到集合中,因为私有的变量在整个类模块中都是可见的。

详细信息   该示例在“创建自己的集合类:砖块盖的房子”中将继续讨论。