Creating Your Own Collection Class: The House of Bricks

See Also

This topic continues the code example begun in "Public Collection Example: The House of Straw" and "Private Collection Example: The House of Sticks." You may want to read those topics before beginning this one.

The most robust way to implement a collection is by making it a class module. In contrast to the preceding examples, moving all the code for object creation into the collection class follows good object design principles.

This example uses the same form and the same Employee class module as the previous examples. Insert a new class module, and set its Name property to "Employees." Insert the following declarations and code into the new class module.

Option Explicit
Private mcolEmployees As New Collection

The Add, Count, and Delete methods of the Employees class are essentially the same as those of the old SmallBusiness class. You can simply remove them from the SmallBusiness class module, paste them into the Employees class module, and change their names.

The names can change because it's no longer necessary to distinguish EmployeeAdd from, say, CustomerAdd. Each collection class you implement has its own Add method.

' Methods of the Employees collection class.
Public Function Add(ByVal Name As String, _
ByVal Salary As Double) As Employee
   Dim empNew As New Employee
   Static intEmpNum As Integer
   ' Using With makes your code faster and more
   ' concise (.ID vs. empNew.ID).
   With empNew
      ' Generate a unique ID for the new employee.
      intEmpNum = intEmpNum + 1
      .ID = "E" & Format$(intEmpNum, "00000")
      .Name = Name
      .Salary = Salary
      ' Add the Employee object reference to the
      ' collection, using the ID property as the key.
      mcolEmployees.Add empNew, .ID
   End With
   ' Return a reference to the new Employee.
   Set Add = empNew
End Function

Public Function Count() As Long
   Count = mcolEmployees.Count
End Function

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

The Employees method of the SmallBusiness object becomes the Item method of the collection class. It still delegates to the Collection object, in order to retrieve members by index or by key.

' Method of the Employees collection class.
Public Function Item(ByVal Index As Variant) _
As Employee
   Set Item = mcolEmployees.Item(Index)
End Function

There's a nice touch you can add here. By making Item the default method of the Employees class, you gain the ability to code Employees("E00001"), just as you could with the Collection object.

To make Item the default property

  1. On the Tools menu, click Procedure Attributes to open the Procedure Attributes dialog box. In Name box, select the Item method.

  2. Click Advanced to show the advanced features. In the Procedure ID box, select (Default) to make the Item method the default. Click OK.

    Note   A class can have only one default member (property or method).

Enabling For Each … Next

Along with robustness, you get For Each … Next back. Once again you can delegate all the work to the Collection object, by adding the following method:

' NewEnum must return the IUnknown interface of a
' collection's enumerator.
Public Function NewEnum() As IUnknown
   Set NewEnum = mcolEmployees.[_NewEnum]
End Function

The important thing you're delegating to the Collection object is its enumerator. An enumerator is a small object that knows how to iterate through the items in a collection. You can't write an enumerator object with Visual Basic, but because the Employees class is based on a Collection object, you can return the Collection object's enumerator — which naturally enough knows how to enumerate the items the Collection object is holding.

The square brackets around the Collection object's _NewEnum method are necessary because of the leading underscore in the method name. This leading underscore is a convention indicating that the method is hidden in the type library. You can't name your method _NewEnum, but you can hide it in the type library and give it the procedure ID that For Each … Next requires.

To hide the NewEnum method and give it the necessary procedure ID

  1. On the Tools menu, click Procedure Attributes to open the Procedure Attributes dialog box. In Name box, select the NewEnum method.

  2. Click Advanced to show the advanced features. Check Hide this member to make NewEnum hidden in the type library.

  3. In the Procedure ID box, type –4 (minus four) to give NewEnum the procedure ID required by For Each … Next. Click OK.

    Important   In order for your collection classes to work with For Each … Next, you must provide a hidden NewEnum method with the correct procedure ID.

Not Much Left of the SmallBusiness Class

The SmallBusiness class will have considerably less code in it now. To replace the Collection object and all the methods you removed, there's a new declaration and a read-only property:

Option Explicit
Private mEmployees As New Employees

Public Property Get Employees() As Employees
   Set Employees = mEmployees
End If

This deserves a word of explanation. Suppose for a moment that you left out the Property Get, and simply declared Public Employees As New Employees.

Everything would work fine as long as nobody made any mistakes, but what if you accidentally coded Set sbMain.Employees = Nothing? That's right, the Employees collection would be destroyed. By making Employees a read-only property, you avert that possibility.

Changes to the Form

The code for the form module is very similar to the preceding example. You can use the same module-level declarations, and the Click event for the Close button is the same.

The only change in most of the event procedures is replacing the old methods of the SmallBusiness class with the new methods of the Employees collection object:

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

Private Sub cmdEmployeeDelete_Click()
   ' Check to make sure there's an employee selected.
   If lstEmployees.ListIndex > -1 Then
      ' The first six characters are the ID.
      sbMain.Employees.Delete _
      Left(lstEmployees.Text, 6)
   End If
   cmdListEmployees.Value = True
End Sub

Private Sub cmdListEmployees_Click()
   Dim emp As Employee
   lstEmployees.Clear
   For Each emp In sbMain.Employees
      lstEmployees.AddItem emp.ID & ", " & emp.Name _
      & ", " & emp.Salary
   Next
End Sub

Notice that you can use For Each … Next again to list the employees.

Run the project and verify that everything works. There's no code for the Trouble button this time, because encapsulation has banished trouble.

For More Information   Read "The Visual Basic Collection Object" and "Collections in Visual Basic" for background on collections.

The Class Builder utility included in the Professional and Enterprise editions will create collection classes for you.

The lessons of the House of Straw, House of Sticks, and House of Bricks examples are summed up in "The Benefits of Good Object-Oriented Design."