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, the end result was a collection class that performed the exact same function as the one provided out of the box.   Well hopefully all will become clear in the next 20 minutes or so as we go through the process of extending the functionality of a custom collection class so that consumers can sort the contents of the class as well as provide the ability to read the keys of the items in the class – a feature not available in the Visual Basic Collection Class.

Open the Collection Class project.  If you examine the code in your Form you’ll see the routine AddItem

'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) >= 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

Next change the Add Method so that it calls this routine.


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

Note, we have changed the method signature.  We had two optional parameters in this routine which we’ve now changed.   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 its 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.

In our Form, change the AddItem method 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 an architecture and I can describe this architecture in a diagram.

two layer architecture

Our code 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 and the developers will typically implements from these diagrams.  Obviously ours is a simplified example, we only have one class, the VBBookCollection class, and one item in the presentation layer, Form1.  We could have numerous consumers of the class with this architecture.

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.  Lets 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

' 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) >= 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 lines underlined lines are adding the keeping the ISBN collection in synch with the BookNames collection.    One, a collection of ISBNs and the other, in the same order, a collection of book names.

This enables all consumers to read the key of the book.  We just need to add a new method

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

Now a consumer of this class can read the name of a book and it’s ISBN.  This is probably not the best solution long term, however when we call the code in our Form, it only appears like we have one collection.  The implementation is hidden from the consumer of the code.  And that’s one of the true beauty of classes.  As long as we keep the interface consistent, we can modify and improve the code in the class as we see fit without touching the code in the Form.

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).ToString
     Next

End Sub

Adding both the Key and the Item to the Listbox.  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 have multiple consumers of this code.  We can now enhance this code, improving the performance and other internal workings, without affecting our consumers.

That my friends is why we use Classes in Visual Basic.