dcsimg
December 8, 2016
Hot Topics:

Book Review: Beginning Visual Basic 6 Objects

  • November 19, 2002
  • By James Limm
  • Send Email »
  • More Articles »

What is Inheritance?

With inheritance, a class gains all the properties and methods that make up the interface of the base class, and it can then extend the interface by adding properties and methods of its own. The new class can also extend or change the implementation of each of the properties and methods from the original base class.

Inheritance, in a nutshell, would mean that we could create an object and then use it (plus all its properties and functionality) as the basis for a new object.

For instance, if we had a Vehicle class that had all the properties and methods that apply to all types of vehicle, then inheritance would make it very easy to create classes for Car, Van and Semi. We wouldn't need to recreate all the code for a Car to be a Vehicle: it would inherit that automatically from the original Vehicle class. This technique could be very useful for comparing say, properties of Cars and Vans (such as color, number of wheels etc) – even though cars and vans are fundamentally different objects.

Inheritance at DataDamage Inc.

Consider our application for DataDamage Inc. If our application were a little bigger, then at some point we would reach a worrying situation. Given that the place where we stored the data would sooner or later change, we would eventually have to go through and change all our data access code to deal with that change. We would have work to do in both in the clsAuthors class and the clsTitles class - not to mention any other classes that may have been added over time which access the database directly. Not a pleasant situation, especially if you scale up the size of our application.

Inheritance would solve our dilemma here. We could simply put all the common functionality for accessing the database in one object, and then build on that object with new ones that inherited the old. This would be ideal in our case - we could have a clsRecordset object holding a lot of code to talk to the database, and then we could inherit that object into clsAuthors and clsTitles, just adding any appropriate code specific to those tables.

It seems we don't have that luxury yet, however. Maybe in a future version of Visual Basic we will find inheritance. For now, is all this just wishful thinking? Read on.

Interface Inheritance

As we've seen, VB6 comes complete with a fantastic command: Implements. When people first saw this command, the immediate reaction from a lot of them was to jump up and down proclaiming that Microsoft had at last brought inheritance to Visual Basic. This was not the case.

We can, however, gain interface inheritance by using the Implements keyword. The Implements keyword just tells Visual Basic that we would like to implement code for another object's interface - we don't inherit the original object's data or behavior. However, the big drawback is that we can't extend an interface that has been created with the Implements keyword, so with this method we don't gain one of the key benefits of inheritance.

So how do we use interface inheritance? Take a look at this example of a Vehicle class:

Option Explicit

Public Property Get Wheels() As Integer
End Property

Public Property Get NumberOfSeats() As Integer
End Property

Public Property Get Color() As String
End Property

Notice that there isn't any code in these routines. Remember that interface inheritance allows the inheritance of interfaces only. Any code in these routines would not be used anyway.

Now we can create classes based on the Vehicle class by using the Implements keyword to inherit the interface. Take a look at this Van class:

Option Explicit

Implements Vehicle

Private Property Get Vehicle_Wheels() As Integer
Vehicle_Wheels = 4
End Property

Private Property Get Vehicle_NumberOfSeats() As Integer
Vehicle_NumberOfSeats = 2
End Property

Private Property Get Vehicle_Color() As String
Vehicle_Color = "White"
End Property

We could do the same with a Car class:

Option Explicit

Implements Vehicle

Private Property Get Vehicle_Wheels() As Integer
Vehicle_Wheels = 4
End Property

Private Property Get Vehicle_NumberOfSeats() As Integer
Vehicle_NumberOfSeats = 5
End Property

Private Property Get Vehicle_Color() As String
Vehicle_Color = "Red"
End Property

Notice that we have to prefix the properties Wheels, NumberOfSeats and Color with Vehicle_ so that Visual Basic knows that these routines belong to the Vehicle interface. Unfortunately we can't extend the interface, so if we want to add a property to the Van class for StorageCapacity we'll have to use a different technique.

Simulated Inheritance

It's possible to simulate inheritance by using a combination of two techniques, containment and delegation. So what do these terms mean?

The idea behind containment is that, in object-oriented analysis, an object can have a private instance of another object inside itself. For instance, a old Television object could contain a Valve object, but that Valve object would be private to the Television object, since no code outside of that Television object would be likely to interact with that Valve.

Delegation refers to the situation where one object delegates a task to another object rather than doing the work itself. Now when we elect not to call the methods in the base class, we are choosing not to delegate tasks down to the base class - and we write methods within the current class to perform the task instead.

Let's see how we can simulate inheritance with our Vehicle example:

Option Explicit

Public Property Get Wheels() As Integer
Wheels = 4
End Property

Public Property Get NumberOfSeats() As Integer
NumberOfSeats = 5
End Property

Public Property Get Color() As String
End Property

This time we've put some code into our Wheels and NumberOfSeats routines. With simulated inheritance these properties will be available in any new classes we create from Vehicle.

Now let's create a new Car class.

Option Explicit

Private objVehicle As Vehicle
Public Property Get Wheels() As Integer
Wheels = objVehicle.Wheels
End Property

Public Property Get NumberOfSeats() As Integer
NumberOfSeats = objVehicle.NumberOfSeats
End Property

Public Property Get Color() As String
Color = "Red"
End Property

Notice that this time we don't use Implements and that we create a Private Vehicle object inside our new Car class. Our Car class contains an instance of the Vehicle class.

Private objVehicle As Vehicle

In our Wheels and NumberOfSeats routines, the Car class is delegating the work down to the private Vehicle object.

NumberOfSeats = objVehicle.NumberOfSeats

Now let's create a new Van class in the same way:

Option Explicit

Private objVehicle As Vehicle

Public Property Get Wheels() As Integer
Wheels = objVehicle.Wheels
End Property

Public Property Get NumberOfSeats() As Integer
NumberOfSeats = 2
End Property

Public Property Get Color() As String
Color = "White"
End Property

Public Property Get StorageCapacity() As String
StorageCapacity = "A Lot"
End Property

Now in this case the NumberOfSeats for a Van is different, so there is no delegation down to the Vehicle object. Instead, the Van overrides this functionality by implementing the routine itself.

NumberOfSeats = 2

We've also extended our interface for the Van object by adding a StorageCapacity property - something we were unable to do with interface inheritance.

Public Property Get StorageCapacity() As String

StorageCapacity = "A Lot"

End Property

Containment at DataDamage Inc.

In our application for DataDamage, the combination of containment and delegation allows us to move the basic functionality of the clsTitles and clsAuthors classes back into clsRecordset. We can then call that functionality from the methods in clsTitles and clsAuthors.

Try It Out - Using Containment in Our BiblioTech Project

1 Let's begin by moving the basic functionality that was in the clsTitles and clsAuthors into clsRecordset. Change the clsRecordset class so that it looks like this:

Option Explicit
' clsRecordset object - a generic object designed to wrap
' up the functionality of a recordset in a generic re-useable
' object.

' Class methods

Public Sub MoveNext(recRecords As Recordset)
If Not recRecords.EOF
recRecords.MoveNext
End If
End Sub

Public Sub MovePrevious(recRecords As Recordset)
If Not recRecords.BOF Then
recRecords.MovePrevious
End If
End Sub

Public Sub MoveLast(recRecords As Recordset)
recRecords.MoveLast
End Sub

Public Sub MoveFirst(recRecords As Recordset)
recRecords.MoveFirst
End Sub

2 Now we come to the interesting part! We're going to use containment in clsTitles, so that a private instance of clsRecordset is contained within our clsTitles object.

The top of our clsTitles class thus becomes:

Option Explicit

' clsTitles - wraps up the Biblio Titles table. By keeping the
' interface consistent it should be possible to move the class
' to point at some other data source, such as an RDO Resultset,
' or flat file, without users of the class ever noticing.

Public Event DataChanged()

' Public members to implement properties

Public strTitle As String
Public intYear_Published As Integer
Public strISBN As String
Public lngPubID As Long
Public strDescription As String
Public strNotes As String
Public strSubject As String
Public strComments As String
Private m_objRecordset As New clsRecordset

' Private members to hold connection to database,
' and to the recordset itself

Private m_recTitles As Recordset
Private m_dbDatabase As Database

Notice we've removed the Implements command from this code now: we no longer need to implement clsRecordset in clsTitles, since we can access the clsRecordset routines through our new m_recRecordset private member variable, which contains the private instance of the clsRecordset object.

3 Now we're able to remove the basic functionality from our clsTitles class - remember, we're delegating the recordset navigation tasks to our clsRecordset class. The end of your clsTitles class should look like this:

' Class methods 

Public Sub MoveNext()
m_objRecordset.MoveNext m_recTitles
Reload_Members
End Sub

Public Sub MovePrevious()
m_objRecordset.MovePrevious m_recTitles
Reload_Members
End Sub

Public Sub MoveLast()
m_objRecordset.MoveLast m_recTitles
Reload_Members
End Sub

Public Sub MoveFirst()
m_objRecordset.MoveFirst m_recTitles
Reload_Members
End Sub

All we're doing here is delegating the navigation of the recordset held in the m_recTitles member variable, to our private instance of clsRecordset (i.e. m_objRecordset).

Notice also that we've deleted the following routines from our class. As we're using a combination of containment and delegation to simulate inheritance, instead of the Implements keyword, we're now able to extend the interface of our clsAuthors class. Therefore, these routines are no longer needed in our clsTitles class.

Public Sub clsRecordset_AddAsNew()
' Do nothing
End Sub

Public Sub clsRecordset_SaveChanges()
' Do nothing
End Sub

Public Sub clsRecordset_DeleteCurrent()
' Do nothing
End Sub

4 Now we must repeat the process with the clsAuthors class. Change the code at the beginning of clsAuthors so that it looks like this:

Option Explicit

' clsAuthors - wraps up the functionality to browse and 
' maintain the authors table in the Biblio database

Public Event DataChanged()
' Public members to implement properties

Public lngAu_ID As Long
Public strAuthor As String
Public intYear_Born As Integer
Private m_objRecordset As New clsRecordset

' Private members to hold connection to database,
' and to the recordset itself

Private m_recAuthors As Recordset
Private m_dbDatabase As Database

Once again, the Implements command has been removed and we've declared a member variable m_objRecordset to hold our private instance of the clsRecordset object. No surprises here!

5 Now move to the bottom of the clsAuthors class and make the following changes:

' Class methods

Public Sub MoveNext()
m_objRecordset.MoveNext m_recAuthors
Reload_Members
End Sub

Public Sub MovePrevious()
m_objRecordset.MovePrevious m_recAuthors
Reload_Members
End Sub

Public Sub MoveLast()
m_objRecordset.MoveLast m_recAuthors
Reload_Members
End Sub

Public Sub MoveFirst()
m_objRecordset.MoveFirst m_recAuthors
Reload_Members
End Sub

Public Sub SaveChanges()
With m_recAuthors
.Edit
Save_Members
.Update
End With
End Sub

Public Sub AddAsNew()
With m_recAuthors
.AddNew
Save_Members
.Update
End With
End Sub

Public Sub DeleteCurrent()
m_recAuthors.Delete
On Error Resume Next
m_recAuthors.MoveFirst
Reload_Members
End Sub

Just as with our clsTitles class, we can remove the clsRecordset_ prefix from our method names. In addition, as the code for navigating the recordset is held within the clsRecordset class we just need to delegate these tasks to the base object, m_objRecordset.

Finally, we've extended the interface of our clsAuthors class with the AddAsNew, SaveChanges and DeleteCurrent routines. These routines need to be Public as they're being called from our frmAuthor form.

Now all that remains to be done is to make some minor alterations to our browsing forms.

6 Open the code window for frmTitle and change your code so that it looks like this:

Option Explicit

Private WithEvents Titles As clsTitles

Private Sub cmdFirst_Click()
Titles.MoveFirst
End Sub

Private Sub cmdLast_Click()
Titles.MoveLast
End Sub

Private Sub cmdNext_Click()
Titles.MoveNext
End Sub

Private Sub cmdPrevious_Click()
Titles.MovePrevious
End Sub

7 Now repeat the process with the frmAuthor form - it should look like this:

Option Explicit

Private WithEvents Authors As clsAuthors
Private Sub Authors_DataChanged() ' When the data in the Authors object changes (by moving to a new ' record, update the controls on the form with the revised data txtAuthor = Authors.strAuthor txtYearBorn = Authors.intYear_born End Sub Private Sub cmdAdd_Click() Update_Authors Authors.AddAsNew End Sub Private Sub cmdDelete_Click() Authors.DeleteCurrent End Sub Private Sub cmdFirst_Click() Authors.MoveFirst End Sub Private Sub cmdLast_Click() Authors.MoveLast End Sub Private Sub cmdNext_Click() Authors.MoveNext End Sub Private Sub cmdPrevious_Click() Authors.MovePrevious End Sub Private Sub cmdUpdate_Click() Update_Authors Authors.SaveChanges End Sub

8 Now run the project! You shouldn't notice any difference as a user - but there's a whole world of difference in how the application is structured under the hood.

For the DataDamage application on its present scale, there wouldn't really be much code benefit to us at this stage in using these techniques. In the real world, though, should the database ever need to be moved to somewhere other than a nice simple Access database, these techniques would take on a new significance. Our trump card would be that we could easily redesign that base object in such a way that nothing else in the application would have to change.

Naturally, the code in the base class would have to change to reflect the change in database, but those changes would ripple up to any other classes which contained its functionality - and from there the changes would ripple up to the forms that used those classes. Now THAT is a maintainable system!

Here's a thought for you. Moving all that code out of the original class and into a new one might seem like a lot of work if the classes we were dealing with were a little larger and more complex. So what's the point? Well, the point does depend on your project and your needs. That's why we've looked at the different ways you can implement objects in Visual Basic. But I do have a real-life example that might help you make your mind up...

I did some consultancy for a large company that had a team of 10-20 inexperienced Visual Basic developers. In order to reduce the burden on them (they were all newbies after all) the Management decided that we would develop boilerplate code, which was pre-built blocks of code that the other developers would just fill in the blanks on. My suggestion was "Hey, why don't we do something with interface inheritance and containment of objects - that's going to save us a lot of time and effort, and the Visual Basic compiler can even help us enforce it all." The management answer was the age old "We don't have time to do that... it's too new."

The end result then was that we got on with the management's "quicker" solution. The rest of the team then took our boilerplate forms, added their minor changes, and then added them into the grand project. There were more than 90 forms in that project, all of which were based on the boilerplate code. Every time that boilerplate changed, the programmers had to stop and make the changes to their working forms by hand. Then they had to retest the forms for errors. This process took about an hour per form, each and every time the base code changed, which averaged out at once a month.

So, the management decision that we couldn't afford 2 days at the start to get it right resulted in them spending approximately 45 man days retro-fitting their code with boilerplate changes.

The moral of my story is this: containment may take some extra time in the short term, but in the long term it's usually worth every minute.

You have been warned.





Page 8 of 9



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel