Extending the functionality of a custom collection class

In the last tutorial we learnt how to create our own custom Collection Class in Visual Basic.  We developed a wrapper over the Visual Basic Collection Class provided out of the box.  If you completed the exercise you would have probably wondered why, after all that work, you had a Collection Class that performed the exact same function as the one Microsoft provided out of the box.   The beauty of creating our own Class is that we can extend and change this functionality to suit our needs.  One extension will be to sort the contents of the Class.  In addition, the Visual Basic Collection Class has a pretty significant limitation; it’s impossible to read the key of an item after it’s been added to the Collection.  Let’s fix that in the next 20 minutes or so.

If you remember from the Visual Basic Collection Class tutorials, we developed a routine to sort the contents of the Collection Class.  We developed the routine in the Main Form and it looks as follows:

'Add an item at its correct position.
Private Sub AddItem(ByVal item As String, ByVal key As String)

     Dim i As Integer

     'See where the item belongs.  Iterate through the entire list
     For i = 1 To BookCollection.Count
          If BookCollection.Item(i) >= item Then
               Exit For
          End If
     Next i

     'Insert the item.
     If i > BookCollection.Count Then
          ' Add at the end.
          BookCollection.Add(item, key)
     Else
          ' Add at the right position.
          BookCollection.Add(item, key, i)
     End If
End Sub

This is a routine to sort the Collection Class, or more accurately, ensure that as items are added, they are added in alphabetical order.

Copy this Subroutine and paste it into your VBBookCollection Class.  You’ll immediately see all sorts of Intellisense errors stating that BookCollection isn’t declared anywhere accessible by that Class.

Replace the references to BookCollection to col, the Visual Basic Collection Class maintained by our custom Class.  The routine should look something like this:

' Add an item at its correct position.
Private Sub AddItem(ByVal item As String, ByVal key As String)

     Dim i As Integer

     ' See where the item belongs.  Iterate through the entire list
     For i = 1 To col.Count
          If col.Item(i).ToString >= item Then
                Exit For
          End If
     Next i

     ' Insert the item.
     If i > col.Count Then
          ' Add at the end.
          col.Add(item, key)
     Else
          ' Add at the right position.
          col.Add(item, key, i)
     End If
End Sub

Note, the following code:

If col.Item(i) >= item Then

had to be changed to the following:

If col.Item(i).ToString >= item Then

The reason for this change is that our BookCollection.Item Method returns a String, however the Visual Basic Collection Class Item Method returns an Item of type Object.  We know that all of the Items in our Collection are Strings, so we can safely convert ToString.

In the same VBBookCollection Class, change the Add Method so that it calls this new AddItem routine.


Public Sub Add(ByVal item As String, ByVal isbn As String)
     AddItem(item, isbn)
End Sub

Note, we also changed the Method signature.  We had two optional parameters in this Add routine which we’ve now removed.   Any code consuming of this Class which previously made use of the optional parameters will now not work.  Visual Basic will generate a syntax error.  In a live environment, changing a Method signature can be exceptionally dangerous and it’s often better to overload your Method.  In our application however we know exactly where the code is called, and we know the application isn’t live.  This is a fix we can make with 100% confidence.  Nothing will break as we will now fix the calling code.  The code that calls this Add Method is in the Form in the AddItem routine.  Change this to:

Private Sub AddItem(ByVal item As String, ByVal key As String)
     BookCollection.Add(item, key)
End Sub

Run the application and what do you get.  Exactly the same again!  🙂

However what we have done here is pretty fundamental.  We’ve separated the code into unique units with distinct responsibility.  Everything that relates to managing the Collection Class is in one unit of code, the VBBookCollection Class.  Everything relating to presenting the Collection Class to the user is in the Form.  We have divided the responsibilities cleanly.  We are obeying the Don’t Repeat Yourself Principle of “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”

Our Form previously was a mangle of Collection Class management and presentation code.  It did everything.  It didn’t have any particular responsibility.  The code is much cleaner now.  I know where the code is to manage the Collection Class.  If this turned into a multi-user Project, my colleagues would know where to find the code to manage the Class.  We’ve added structure to the code.  Rather than having everything in one place, our application has a coherent architecture and I can describe this architecture in a diagram.

two layer architecture

Our application has two layers, one for the Presentation and one for managing the Collection.   Application Architects will typically design an application on this level, describing the roles and responsibilities of the various blocks and layers of code.  The developers will typically implement these diagrams into working code.  Obviously this is a simplified example compared to most real world scenarios.  For one, we only have one Class, the VBBookCollection Class.  The application also only has one item in the presentation layer, Form1.  However, with this architecture, we could have numerous consumers of the Class.

If you remember, one weakness of the VB Collection Class was the inability to retrieve the keys of the Collection.  We have no way to read the keys once added.  Let’s put that right.  We know exactly where to modify the code – the VBBookCollection Class.

Open the VBBookCollection Class and replace the name of the Variable col with the name colBookNames.  In Visual Studio 2010 you can do this by selecting the Variable name, right clicking, click Rename and enter the new name.

In addition, create a new Collection called colISBN so we now have two Collections in the Class:

     Private colBookNames As Collection = New Collection
     Private colISBN As Collection = New Collection

Change our Remove Method so we are removing items from both Collections in sync.

Public Sub Remove(ByVal isbn As String)
     colBookNames.Remove(isbn)
     colISBN.Remove(isbn)
End Sub

And add two new lines of code to the AddItem Method, ensuring an item is added to the ISBN Collection when an item is added to the BookNames Collection.

' Add an item at its correct position.
Private Sub AddItem(ByVal item As String, ByVal key As String)

     Dim i As Integer

     ' See where the item belongs.  Iterate through the entire list
     For i = 1 To colBookNames.Count
          If colBookNames.Item(i).ToString >= item Then
               Exit For
          End If
     Next i

     ' Insert the item.
     If i > colBookNames.Count Then
          ' Add at the end.
          colBookNames.Add(item, key)
          colISBN.Add(key, key)
     Else
          ' Add at the right position.
          colBookNames.Add(item, key, i)
          colISBN.Add(key, key,i)
     End If
End Sub

The two new lines are keeping the ISBN Collection in synch with the BookNames Collection.

The following Key Method takes advantage of this new ISBN collection.

Public Function Key(ByVal index As Integer) As String
     Return colISBN.Item(index)
End Function

This Key Method allows a consumer of this Class to read both the name of a book and it’s ISBN.  Maintaining two Collections in synch like this is probably not the best technical solution long term.  However, to our Form, it appears like we one Collection only.  The inner workings of the class are hidden from the consumer of the code.  That’s one of the true beauties of classes.  As long as we keep the interface consistent, we can modify and improve the inner workings of a Class as we see fit without touching the consumers of that Class.

To consume the new Method, go to the Form and change the AddBooksToListBox Method to:

Private Sub AddBooksToListBox()

     Dim i As Integer

     For i = 1 To BookCollection.Count
          ListBoxBooks.Items.Add(BookCollection.Key(i) & " - " & BookCollection.Item(i)
     Next

End Sub

This code adds both the Key and the Item to the ListBox.

Take a look at the Form Load Event.  There’s a block of code that looks as follows:

        AddItem("0735626693 - Microsoft Visual Basic 2010 Step By Step", "0735626693")
        AddItem("0672336294 - Sams Teach Yourself Visual Basic 2012 in 24 Hours, Complete Starter Kit", "0672336294")
        AddItem("0735608334 - Distributed Applications with Microsoft Visual Basic 6.0 McSd Training Kit : For Exam 70-175", "0735608334")
        AddItem("0735608334 - Microsoft® ASP.NET Programming with Microsoft Visual Basic® .NET Version 2003 Step By Step", "0735619344")
        AddItem("0201702657 - Visual Basic 6 Design Patterns", "0201702657")
        AddItem("0470503696 - Excel VBA Programming For Dummies", "0470503696")
        AddItem("1902745000 - Learn to Program with Visual Basic", "1902745000")
        AddItem("0782124690 - Visual Basic 6 Complete", "0782124690")

We no longer need to fudge a solution by adding the ISBN and the Name separated by a dash in this way.  We can just add the name and the key and read the key in the same way we read the name. We now have a Collection Class that has more relevant functionality to our needs than the one provided out of the box.   We only had to change one line of code in our presentation layer to make use of this code.   We can now enhance this code, improving the performance and other internal workings, without affecting our consumers.  Plus, we can now have multiple consumers of this code.

That, my friend, is why we use Classes in Visual Basic.