Visual Basic Explorer
Visual Basic Explorer
 Navigation
 Home


 Coding
 Source Code

 FAQ Center

 VB Tips

 Downloads

 ToolBox

 Tutorials

 VB Games

 VB News

 VB Award

 VB Forums



 Affiliates
 Planet Source Code

 Rent a Coder

 DirectX4VB


 Misc
 Search

 Feedback

 Advertise

 About


Need to hire
a VB coder?

Please support our sponsor:

 Home 
 Site Map 
 Forums 
 News 
 Feedback 

Chapter 3 – OO Development

Many thanks to the good people of Wrox Press for graciously allowing us to post this tutorial from "Beginning VB6 Objects" book by Peter Wright. This tutorial is Chapter 3 of the book.

Note: I did the converting and formatting of this page so if the material isn't pretty it's my fault and is not indictative of the quality of Wrox's books -I have found them to be excellent. In addition some of the graphics are the authors original sketches which were replaced by professional graphics in the book. In spite of any lacks in my conversion of this document to HTML, I am sure that you will be able to see why Peter Wright's books are so popular.


Overview

In this chapter, we're going to explore some of the key programming techniques that will take us to the heart of object-oriented development with Visual Basic.

We saw, in the last chapter, just how badly things can go wrong without the good principles of object-oriented programming behind us. And we're familiar, now, with some of the key benefits of working with objects - so the techniques we learn here will be prepare us well for our future programming careers.

We're actually going back to another company, but this time I think you'll find that DataDamage Inc. are a much more aware bunch of people, and that they appreciate the value of the object-oriented techniques available to today's Visual Basic programmers.

In particular, in this chapter we'll be looking at:

  • Developing a real-world object-oriented application
  • Object Interfaces
  • Multiple interfaces
  • The Implements keyword
  • Inheritance
  • Containment and Delegation

Real Object-Oriented Programming

With Visual Basic 6, objects are here to stay. Love them or hate them, we can't get away from them. Without exception, everything we deal with in Visual Basic is an object. The forms we draw our user interfaces on, the controls that we put on to the form, and even most of the system objects that we use, from time to time, to complete the shine on our applications - they are all objects.

Visual Basic's support for object-oriented development has come a long way since previous versions. We now have the ability to expose more than one interface from within an object, thanks to the Implements keyword (more on that later), and we can create our own custom events at runtime. Visual Basic has a lot to offer real-world programming situations.

The problem with the whole thing, though, is that the object-oriented programming world is still populated by PhD-toting gangsters. These guys would have you believe that objects represent a different world, as opposed to just a neat way to design and develop applications. We covered a lot of the theory in my previous book (Beginning Visual Basic 6, also published by Wrox Press), but this time around we're going to see it used in anger, see some real-world development done with object-oriented programming.

Let me tell you: OOP isn't that hard. It doesn't need that much in the way of background training, or patterns of deep thought. It's all a matter of common sense. Let's take a look at a real problem.

Welcome to DataDamage Inc

Our client, DataDamage Inc, has decided that something needs to be done to keep abreast of their Biblio database, which is a database containing a vast array of information on programming books, authors and publishers. They've decided to break down the development of this project into phases, with Phase 1 covering nothing more than the maintenance of the Authors table in the database, as well as simple browsing of the Titles table.

Sounds simple enough - nothing a few well-placed Data controls and some bound Text boxes won't handle. But wait a minute. Although the database is currently in Access format, a quick peek at the long-term plans for the system reveals some worrying elements. Not only are DataDamage going to want to migrate this system from an Access database up to a SQL Server database at some point in time, but they're also going to be using the code we write as the foundation for other developers to get started on the system. Anything we do now must remain useful under the new architecture.

In addition, it's quite likely that they will want to split the application itself across multiple machines on the network to improve the performance of the system. A few data controls and a couple of bound Text boxes aren't going to be much help here.

From Design to Object Analysis

Normally, at this point, we'd hit the paper and produce a full design of the application. This design would be from the point of view of the way things are done now (user requirements), the way they should be done in the new system (system requirements), and the way that it's all going to actually work (functional requirements).

My goal within this chapter, however, is to concentrate first and foremost on getting us through the technical aspects of OO development with Visual Basic. As we saw in Chapter 1, program design is a crucial part of object-oriented development, which is why I've devoted the whole of Chapter 9 to design techniques.

In this chapter, I'm going to speed us through the design phase so we can focus on the practical aspects of object-oriented development. If you're madly keen to learn about object-oriented program design, then take a peek at Chapter 9.

So the simple design I've prepared for us in this chapter looks like this:

 

We have a data source (the Biblio database), which is going to be accessed by two classes. As we saw in the last chapter, the beauty of classes is that we can hide the data source in a neat black box with a simple interface.

Remember, an interface in object-oriented terms is the collection of methods, properties and events that go towards unleashing the power of our objects in a way which is easy to get to grips with.

These classes are then used in two forms to provide the Title Browser and Author Maintenance functions that we'll require.

Our next step (and this is really where we swing back into the design process), is to come up with the interface for these objects: the properties and methods that will be used to provide access to the underlying data. In the interest of keeping things real simple for the moment, let's just take a look at the Titles class:

Method

Description

MoveFirst

Moves to the first title in the data source

MoveNext

Moves to the next title in the data source

MovePrevious

Moves to the previous title in the data source

MoveLast

Moves to the last title in the data source

Pretty clear I think: we're going to provide some very similar functionality to something we might find in a recordset object itself, albeit without the ability to update the underlying data. Ah, the data... we're going to need some properties in there to get at that underlying data:

Properties

Description

BOF

Boolean to indicate if we're at the beginning of the Titles table

EOF

Boolean to indicate if we're the end of the Titles table.

Title

The Title field from the Titles table

Year_Published

The Year Published field from the Titles table

ISBN

The ISBN field - Titles table

PubID

The PubID field - Titles table

Description

The Description field - Titles table

Notes

The Notes field - Titles table

Subject

The Subject field - Titles table

Comments

The Comments field - Titles table

So there we have it. A simple interface that exposes the underlying data as well as the functionality to manipulate that data.

Creating a Class

Now we've designed our interface, let's go ahead and translate all of these ideas into some real code with some real functionality behind it. Once we've created our class, we'll build a test framework to see it in action.

Try It Out – Creating a Class for DataDamage Inc

1Start up a new project and, when it appears, add a new class module in the usual way. If you can't remember how to do that, then now might be a good time to re-read Chapter 1, or consult my other book, Beginning Visual Basic 6.

2 The next step is to start adding code into our new class module to implement all the methods and properties that we've just discussed. For now, just key in the following code so that your class module looks like mine. By typing this lot in you're sure to come up with some questions of your own as to how things work, and I can go ahead and start answering them when the code is done.

There is a lot of code here, but the interface is real simple, and the bulk of it is quite repetitive and easy to follow. We'll take a closer look at how it works once you've typed it in.

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 members to hold connection to database,
' and to the recordset itself
Private m_recTitles As Recordset
Private m_dbDatabase As Database

' Class event handlers
Private Sub Class_Initialize()
' When an instance of the class is created, we want to
' connect to the database and open up the Titles table.

On Error GoTo Class_Initialize_Error

  Set m_dbDatabase = Workspaces(0).OpenDatabase _
  (App.Path & "\Biblio.mdb")
  Set m_recTitles =   m_dbDatabase.OpenRecordset("Titles", _
  dbOpenTable)

Exit Sub

Class_Initialize_Error:
  MsgBox "There was a problem opening either the Biblio " & _
  "database, or the Titles table.", vbExclamation, "Problem"
Exit Sub

End Sub

Private Sub Class_Terminate()
' When the instance of the class is destroyed, we need to
' close down the recordset, and also the connection to the
' database. Error handling is needed since there could have
' been a problem in the Initialize routine making these
' connections invalid
On Error Resume Next m_recTitles.Close m_dbDatabase.Close End Sub ' Generic Data management methods Private Sub Reload_Members() ' Reloads the member variables (properties) with the field ' values of the current record On Error GoTo Reload_Members_Error With m_recTitles strTitle = "" & .Fields("title") intYear_Published = .Fields("Year Published") strISBN = "" & .Fields("ISBN") lngPubID = .Fields("PubID") strDescription = "" & .Fields("description") strNotes = "" & .Fields("Notes") strSubject = "" & .Fields("subject") strComments = "" & .Fields("Comments") End With RaiseEvent DataChanged Reload_Members_Error: Exit Sub End Sub ' Class methods Public Sub MoveNext() If Not m_recTitles.EOF Then m_recTitles.MoveNext Reload_Members End If End Sub Public Sub MovePrevious() If Not m_recTitles.BOF Then m_recTitles.MovePrevious Reload_Members End If End Sub Public Sub MoveLast() m_recTitles.MoveLast Reload_Members End Sub Public Sub MoveFirst() m_recTitles.MoveFirst Reload_Members End Sub

3 Don't forget to give the class a name when you are done typing. In order for your code to work the same as mine, it makes sense that you set the name property for your new class to clsTitles (reminding us that this is a class related to Titles).

4 You've probably noticed that there's quite a lot of database code in there. In order for this code to compile properly, we need to let Visual Basic know that we intend to use a database, and that it's going to involve setting a reference to VB6's Data Access Objects (DAO).

You may recall, from Chapter 1, that DAO in Visual Basic 6 is an ActiveX component. As such, the DAO doesn't reside within Visual Basic itself, which is why we have to build a reference to it now.

From the Project menu in Visual Basic, select References, and the references dialog will appear. This provides us with a way to show Visual Basic which external objects (objects that live in DLLs or separate EXE files from your own app) that we intend to use. Scroll down the list of items until you see Microsoft DAO 3.51 Object Library and select it, just as in the screenshot below. Then close the dialog down by clicking the OK button:

 


Now that everything is in place for our new clsTitles class to work, we'll take a look at the code and how it works.

How It Works

The first thing that any class needs is a way for its users to get at the important elements of the underlying data. We do this by declaring properties for the class.

Designing an interface for a class involves declaring a set of properties in very much the same way as how we interact with a set of properties when we design a user interface for our applications.

Since this class is supposed to wrap up a lot of functionality to deal with the Titles table in the Biblio database, it makes sense for us to have some properties in there to represent the fields in the underlying database. Then, no matter what format of database we use in the future, programmers using our clsTitles class will know that they can use these field properties to get at the underlying data.

Rather than spend ages typing in Property Let and Property Get routines, we've gone for a simpler approach this time:

' 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

You don't need to key all this in again - we're reviewing the code we just entered for our clsTitles class.

As you can see here, what we're doing here is declaring a set of Public variables within our clsTitles class. Public variables declared in a class in this way are, as far as Visual Basic is concerned, read and write properties. At runtime, a user of our clsTitles class can get directly at the Title property of the object, and at that point they will be reading from, or writing to, these Public variables.

We still have scope though to expand and write full-blown property handlers later, should the need arise.

Having variables acting as properties is great, but we still need code to get data from the database into those variables in the first place. That's where the Reload_Members routine comes in, reading data from the underlying recordset and storing it in our property variables:

Private Sub Reload_Members()
' Reloads the member variables (properties) with the field
' values of the current record
On Error GoTo Reload_Members_Error

With m_recTitles

strTitle = "" & .Fields("title")
intYear_Published = .Fields("Year Published")
strISBN = "" & .Fields("ISBN")
lngPubID = .Fields("PubID")
strDescription = "" & .Fields("description")
strNotes = "" & .Fields("Notes")
strSubject = "" & .Fields("subject")
strComments = "" & .Fields("Comments")

End With

RaiseEvent DataChanged

Reload_Members_Error:
Exit Sub

End Sub

All we're doing here is pulling values from the fields in the m_recTitles recordset and putting them into our member variables. At the end of this, we raise a custom event, which we've called DataChanged, since the user interface needs some way of knowing when these member variables have changed and it needs to update itself.

The m_recTitles recordset itself is opened up when the class is first turned into an object. Take a look at the class initializer:

Private Sub Class_Initialize()
' When an instance of the class is created, we want to
' connect to the database and open up the Titles table.

On Error GoTo Class_Initialize_Error

Set m_dbDatabase = Workspaces(0).OpenDatabase _
(App.Path & "\Biblio.mdb")
Set m_recTitles =   m_dbDatabase.OpenRecordset("Titles", _
dbOpenTable)

Exit Sub

Class_Initialize_Error:
MsgBox "There was a problem opening either the Biblio " & _
"database, or the Titles table.", vbExclamation, "Problem"
Exit Sub

End Sub

This is pretty neat. Our clsTitles class is designed to deal with one specific table in a very specific database, Biblio.mdb. We therefore code the Class_Initialize event to make the class automatically connect to that database:

Set m_dbDatabase = Workspaces(0).OpenDatabase _
(App.Path & "\Biblio.mdb")
Set m_recTitles =   m_dbDatabase.OpenRecordset("Titles", _
dbOpenTable)

This, in turn, reduces the workload for a user of the class: how many of us have written a class, for example, where we require a user of that class to pass in the pre-connected database object ready to use?

A point worth noting, though, is that the code prefixes the name of the database with App.Path. What this means, in English, is that the clsTitles class will expect to find a copy of the Biblio database in the directory the project lives in, or where the compiled program lives when we're finished.

So now might be a good time to run up the Windows Explorer and make sure that there's a copy of the Biblio.mdb database in the same directory that you intend to save this project.

For any programs you write in the future that access databases, if you know the location of those databases then you can specify a particular path within your code, instead of using the App.Path method. In some circumstances, it may even be appropriate to ask the user to enter a pathname for any databases they want your program to work with.

The rest of our code for clsTitles provides an interface to support the common Move commands that we'd find on a recordset; in our case, this means just passing the call down to the recordset itself, before updating the member variables with the new information. Take a look at the MoveNext method, for example:

Public Sub MoveNext()
  If Not m_recTitles.EOF Then
     m_recTitles.MoveNext
     Reload_Members
  End If
End Sub

This code first makes a quick check to see if the end of the table has been reached. If we haven't reached the end of the table, we can move on to the next record - by calling the MoveNext method on the recordset itself. After that, our old friend the Reload_Members method is run, which will move the information from the underlying fields in the new recordset to our clsTitles member properties.

Using Our Class

Ok, let's get some forms up so we can actually use our clsTitles class.

Try It Out – Using Our clsTitles Class

1 Our application is going to be a traditional MDI (Multiple Document Interface) application.

Go ahead and add an MDI form to your project. Once again, if any of this form/module adding stuff seems a little strange then you'd do well to check out my last book, Beginning Visual Basic 6.

Since this is an MDI form, make it big enough to be able to hold the other windows that it's going to contain. If you make it too small, its child windows won't be completely visible within it - which isn't great for our users.

2Set the name of this form to mdiMain. Then select Properties from the Project menu and use the General tab dialog to set the start-up form to be this MDI form, as in the screenshot here:

 

For the sake of completeness, you should also set up the project name to BiblioTech, just as I have done here.

3 Okay, so we have an MDI form in the application, and we have the clsTitles class that neatly wraps up the Titles table. We now need to add a menu to our MDI form that will allow us to access the other forms that we'll be adding to the application.

Add the following menu headings to the MDI form: File, Titles and Authors. Remember, you can do this in Visual Basic by selecting the MDI form in design mode, and then pressing Ctrl and E to bring up the menu editor. There's no need to worry about giving these things neat names since we aren't going to add any code to respond to events on them - it's the submenu items that we're more concerned with. I called mine mnuFile, mnuTitles, and mnuAuthors, respectively:

4Use the menu editor again to add a menu item under the File menu. Set this new menu option caption to Exit and then name it mnuFExit. Underneath the Titles heading, put a Browse item in there and call it mnuTBrowse. Finally, underneath the Authors heading, put a Maintain item on there and call it mnuAMaintain:

5 The next step is to get the actual Title Browsing form up and ready, with code to start it up from the MDI.

As you've probably noticed, there's a lot here for us to do in this phase of our application development - such are the toils of creating a user interface. I'm not an unsympathetic guy! Take a break for this step, and just read what I have to say about user interfaces and object interfaces.

User Interfaces and Object Interfaces

While we're in the middle of building our user interface for DataDamage Inc, I'd like to point out to you how closely related user interfaces and object interfaces are to one another. This is really interesting.

Object-oriented programming does, in fact, fit in really well with the way Windows works. At the root of any programming problem there's a hideous mess of data and an equally unnerving set of code to manipulate that data. Dump that code in a class, though, and we can design a nice interface to that code and data - something more meaningful and more in tune with the real-world problem. Now in terms of the user interface of a Windows application, we usually provide graphical controls on a form that hook into some or all of the interfaces in our classes and objects. This is a case of a user interface marrying an object interface, if you like.

Our Title Browsing form is going to provide users of the application with much the same interface as we can deal with in our objects. For Title Browsing, the interface will of course be in the form of buttons and text boxes, while our classes (such as clsTitles) and then our objects will interface through properties and methods.

6 Let's get that Title Browsing form up and running. Add a standard form to the project, call it frmTitle, and set the MDIChild property on the form to True. By default, at runtime Windows will create this form in a default size, which can totally screw up our user interface.

Rather than write a lot of resize code to make the controls and the form appear as we intended at runtime, simply set the Borderstyle property to Fixed Dialog.

7 When you've done these things, go ahead and drop some controls on the form so that it looks like this:

Set the names of the text boxes to txtTitle, txtYear_Published, txtISBN, txtPubID, txtDescription, txtNotes, txtSubject, txtComments - names that fit quite nicely with the actual properties on the class. Likewise, set the names of the buttons to cmdFirst, cmdPrevious, cmdNext and cmdLast. Notice that I've also set the Caption property of the form to Title Browser - it would be rude not to, after all.

8 In order to get this working, we need to add a little code. Now the important thing to note here is that the hard work is done. We have a class with a very neat interface, and all we now need to do is to literally link the user interface elements to the class interface elements.

Click on the new form to select it, then hit F7 on the keyboard to open up its code window. When it appears, key this code in:

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

Private Sub Form_Load()
  Set Titles = New clsTitles
End Sub

Private Sub Titles_DataChanged()
' When the data changes, we need to load up the text
' boxes on the form

With Titles
  txtTitle = .strTitle
  txtYear_Published = .intYear_Published
  txtISBN = .strISBN
  txtPubID = .lngPubID
  txtDescription = .strDescription
  txtNotes = .strNotes
  txtSubject = .strSubject
  txtComments = .strComments
End With

End Sub

Incredibly simple huh? We just call single methods from the click events on those buttons. Take the First button, for example:

Private Sub cmdFirst_Click()
  Titles.MoveFirst
End Sub

The important part of this code, though, is the event handling.

It is most fortunate that Visual Basic 6 lets us create our own custom events. In the early days of VB, we could write an application that utilized classes, but we would still need to write nasty blocks of code to wait for conditions on our objects to occur. In Visual Basic 6 we can just raise a custom event.

 

We declared the DataChanged event, if you recall, right at the beginning of our clsTitles class like this:

Public Event DataChanged()

which is just a little note telling VB that there's a new event that can occur, and that it's called DataChanged. This event is triggered by the programmer with the RaiseEvent command; in our case, in the Reload_Members method within the clsTitles class:

RaiseEvent DataChanged

No great problems there I hope. However, dealing with the events in code elsewhere in the project does require a little thought. We need to declare the object WithEvents; this means that when we declare an instance of our Titles object, we just do the following (as you'll see right at the top of the code we just added to our Title Browsing form):

Private WithEvents Titles As clsTitles

At this point, the Titles object (which is derived from the clsTitles class) is available in the object drop down at the top of the code window, just as if it were a control on the form.

Using events like this greatly reduces the amount of code in the application. No more endless loops waiting for a specific condition to occur! There is a catch though, as we can see in the form's Form_Load event: when we declare an object WithEvents, we can't create the object at that point in time! So, where we would normally say :

Dim MyObject As New clsMyClass

we actually have to drop the New keyword when we use WithEvents. This isn't a big problem, but it can cause us problems if we forget to actually create the object! That's where the Form_Load event comes in:

Private Sub Form_Load()
  Set Titles = New clsTitles
End Sub

Here the object is actually created, and stored in our Titles object variable, which we declared earlier.

The beauty of objects and events is that they let us deal with the flow of our program in the same way that we might deal with the flow of events in everyday life. Very few people wait specifically for the phone to ring day in, day out: they prefer instead to respond to the phone's ring event.

9Finally, we need to add some code to get the MDI form to load up the Titles browsing form. From the Project explorer window, double click on the MDI form, mdiMain, and select the Browse option from underneath the Titles menu. The code window will appear. Now code the event so that it looks like this:

Private Sub mnuTBrowse_Click()
  Load frmTitle
End Sub

10At this stage, our program works just as we would expect it to. Run it up and have a play with the Title Browsing form to get a feel for what it's doing. There's a lot going on behind the scenes, however, that demonstrates some of the benefits of the object way of working. Just to recap:

  • The application is now independent of its data
  • Black-box development simplifies GUI code
  • Custom events reduce code size at front end

    But there's more good news: Visual Basic's support for object-orientation goes far beyond writing custom events, properties and methods. Let's take a look...

    Interfaces

    We've discussed interfaces a few times, now, when we've been talking about objects, classes, and black boxes; but what exactly is an interface, and why do we care?

    Class Interfaces and Object Interfaces

    Well, we already know that an interface is really nothing more than the name given to the collection of properties and methods within a class. That sounds simple enough, but it's worth looking at interfaces in some more detail now.

    A class interface is the collection of properties and methods within a class: that's the public stuff, the public variables, property routines, subroutines and functions that we put into our classes.

    The private variables and routines within our class, on the other hand, are typically called Member routines.

    It's worth noting that since we create objects from classes in Visual Basic, there is a clear relationship between class interfaces and object interfaces. Once we've created an instance of a class, our object will have an object interface as defined by its underlying class interface.

    The Implements Command

    Visual Basic 6 comes with a special command that lets us do some neat things with class interfaces, and this is the Implements command.

    Placed at the top of a class module, the Implements command forces us to write (or implement) the same methods and properties as those implemented in another class module.

    In effect, this forces us to implement the same class interface for one class as that of another class. Consider this: if the class interface for Class1 consists of three methods and four properties, and we use the following command at the top of a second class called Class2:

    Implements Class1

    then this forces us to implement the same three methods and four properties within Class2 as those defined in Class1.

    However, the methods, properties, etc. that make up a class interface do not have to be implemented using the precise same lines of code that were present in the original class interface.

    The methods in the original class interface must all be defined within the implemented class interface, it's true, but the actual lines of code within those methods may be quite different from the original class interface that's being implemented. This takes us back to the theme of the black box: just as long as the buttons (or methods) on the outside of the black box work, we don't have to worry about what's going on behind the scenes.

    Not every television, for instance, has the same internal workings inside it: all that matters is that the key functions we expect from a television are available to us. And so it is with our class interfaces: the lines of code behind each implementation of a class interface may be quite different.

    If you are still a bit unsure about this idea of implementing, then you could take a quick peek at the section on interface inheritance that appears in a few pages time. It contains a good example of the use of the Implements command.

    Right, I hope you've returned to this point, as we are about to consider the subject of multiple interfaces and see how handy the Implements command can be.

    Multiple Interfaces

    This concept is real simple: a class can support more than one interface.

    Let's stay with our Class1 and Class2 example for a moment. Assuming we'd just implemented the interface for Class1 within our code for Class2, we could then proceed to add a new set of methods and properties to Class2 that were above and beyond the methods and properties implemented from Class1.

    At which point, Class2 happens to support more than one interface: first, the default class interface for Class1; and second, the class interface defined by the new methods and properties that belong to Class2 itself:

    By arranging for a number of classes to implement the same interface, we're forcing the developers of those classes to conform to a particular set of standards. This makes life a lot easier for the people using those classes, since they can expect a certain interface to always be available.

    Think about a car, a small van, and a large semi-truck. All have a common interface: press the gas pedal to go faster, press the brake to slow down, and turn the steering wheel to change direction. Each has at least one door, requires fuel, and contains a set of dials and gauges to show your current speed, fuel remaining, oil pressure and so on.

    All of these classes implement a common interface: a Vehicle interface, if you like. Aside from implementing that Vehicle interface, however, each class has its own unique interface - which it bolts on to the base Vehicle interface. A car has rear seats, for example; a van has the ability to carry more goods than a car, but has less seats; and a semi has a detachable freight carrying device (its trailer), possibly a bed (if it's a long haul truck), and so on.

    Let's carry this line of thought on for a little longer. A car and van could conceivably have the same chassis, the same engine, and to all intents and purposes they could be identical in every way if we ripped the bodywork off. A semi, on the other hand, is completely different underneath the surface. Now since all these cars implement the same interface as a basic Vehicle, we could take any qualified driver and tell them to Drive each of the vehicles from one point to another. Drive is a common method that most drivers know how to use, and the chances are that most drivers could get all three vehicles moving as requested. So despite the differences, and indeed the similarities, between these three vehicles, the Drive method will get us driving - whichever car we're in:

    Visual Visual Basic works the same way. If we have a Vehicle class defined with a Drive method (among others), we can implement the Vehicle interface within new Car, Van and Semi classes. Then, if we want to get our Car, Van and Semi objects moving, Visual Basic will allow us to pass each of these objects to a routine that simply expects a Vehicle type object and then calls its appropriate Drive method.

    Implementing Interfaces in Visual Basic 6

    In our example application for DataDamage Inc, multiple interfaces will be very useful.

    So far, we have a neat title browser. It works quite well, and this browser interface represents something that we would ideally like to see in the Authors class a little later.

    So although we will ultimately need to produce both a clsTitles class (done that) and a distinct clsAuthors class, these classes will have some common methods. For instance, each class will allow the browsing of data. At the same time, however, each class will have its own unique properties, and the clsAuthors class will need to have its own unique methods of changing the underlying Author data (since Author data will be arranged differently in the database than the Title data).

    So here's our plan: we'll define the common browsing interface between these two classes - that is, the interface that contains the MoveFirst, Next, Previous and Last methods. We'll then implement that common browsing interface in both the clsTitles class and the clsAuthors class.

    Anyone who later modifies or deals with the code in our application will be grinning from ear to ear when they see that all data access objects in our application actually have a common object interface.

    So how do we define a common interface between two classes? Well, the best way to do this is to start to think in terms of class frameworks.

    The framework of a class is actually the bare bones of the interface for that class. A framework is therefore just a list of the public routines and properties that form the interface for a class or object, without any real code to actually implement that interface.

    Frameworks are therefore the briefest possible description of a class interface or object interface, since they contain none of the actual code behind that interface - just empty routines and properties. Frameworks are useful things to know about, so let's get on with some code and see it all in action.

    Try It Out – Using Implements

    1 Since our clsTitles and clsAuthors classes are going to share a common interface, the first thing we must do is move the basic framework of the Titles interface out into another object.

    Add a new class module to your project and call it clsRecordset. When that's done, add the following code.

    Option Explicit
    ' clsRecordset object - a generic object designed to wrap
    ' up the functionality of a recordset in a generic re-useable
    ' object.
    
    Private Sub Class_Initialize()
    '
    End Sub
    
    Private Sub Class_Terminate()
    '
    End Sub
    
    ' Class methods
    Public Sub MoveNext()
    '
    End Sub
    
    Public Sub MovePrevious()
    '
    End Sub
    
    Public Sub MoveLast()
    '
    End Sub
    
    Public Sub MoveFirst()
    '
    End Sub
    
    Public Sub AddAsNew()
    '
    End Sub
    
    Public Sub SaveChanges()
    '
    End Sub
    
    Public Sub DeleteCurrent()
    '
    End Sub
    

    Be careful when you define your own interfaces like this though. The Visual Basic compiler has a real nasty habit of wiping out any empty routines that you might have in your project, hence the reasoning behind putting a single quote (comment) in each of the routines above.

    Notice that all these routines are just bare definitions - with no real code in them. Also notice that we're adding three routines to this framework that weren't present within our original clsTitles class (AddAsNew, SaveChanges and DeleteCurrent) which are specifically concerned with adding, saving and deleting data. These are of little relevance for clsTitles, but will feature in our clsAuthors class.

    2 With our clsRecordset framework interface defined, the next step is to actually let Visual Basic start enforcing it on our code - which is a perfect job for the Implements command.

    Bring up the code for your clsTitles class and add a line just underneath the Option Explicit statement so that your code now reads like this:

    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.
    
    Implements clsRecordset
    
    Public Event DataChanged()
    :
    :
    

    If we now try to compile this program and run it, we find that it no longer works: Visual Basic instead gives us a host of new error messages:

     

    I wasn't kidding before, when I said that using Implements tells the compiler to force you to write certain routines! But this is just what we want, in our case, since we need to produce a consistent interface across the clsTitles class and the clsAuthors class that we're going to create shortly!

    You may also notice how useful this could be in a team development environment where you would need to manage a group of developers and make sure that they were all developing to certain interface standards.

    3 The previous error message is telling us that, in order for the clsTitles class to implement the interface of the clsRecordset class, we need to write a method called MoveNext.

    But hang on a second! We did write a MoveNext method; it does exist! If it didn't exist, then the application as it existed before we put that new clsRecordset class in would not have worked at all. The truth of the matter is that Visual Basic is just expecting a little more than we have already done. Time to find out just what.

    Try going back to the code editor for the clsTitles source and drop down the Objects Combo box at the top of the editor:

     

    Notice how the list now includes a clsRecordset object, just as if we had dropped a control on to a form and we were editing the form's code.

    Select clsRecordset from this drop down list, just as I have done here.

    4 Now drop down the Events list Combo box, which sits at the top right of the code window. You'll see a list of all the clsRecordset methods that we can implement in the CTitles class:

    What does all this tell us? Something quite simple really: that our clsTitles class has two interfaces: the original clsTitles class interface, and now also the clsRecordset class interface that we added to clsTitles (we used Implements to do that).

    The upshot is clear enough: all the routines in this drop down list for the clsRecordset interface will need to be defined within our clsTitles class.

    As we noted before, it's perfectly true that there are routines by these names already present in our clsTitles class; but those routines are currently part of the default class interface for clsTitles. We now need to implement the clsRecordset class interface within clsTitles, which we committed ourselves to with the Implements clsRecordset command.

    Next question: how do we implement the clsRecordset class interface within the clsTitles class? Take a look at this routine (but don't type it in yet):

    Public Sub clsRecordset_MoveNext()
      If Not m_recTitles.EOF Then
         m_recTitles.MoveNext
         Reload_Members
      End If
    End Sub
    

    This is our MoveNext routine all right, but notice that it's prefixed with clsRecordset_ which tells Visual Basic that this routine belongs to the clsRecordset interface. This prefix method is how we tell Visual Basic which routines and properties belong to which interface.

    5 Okay, in order to get the clsTitles class working again, go through and change all the Public method names in clsTitles so that they're prefixed with clsRecordset.

    Be careful though: you only need to do this for Public methods that are also in the clsRecordset interface.

    You should end up with all your Public routines, which looked something like this:

    Public Sub MoveFirst()
      m_recTitles.MoveFirst
      Reload_Members
    End Sub
     
    

    now looking more like this:

    Public Sub clsRecordset_MoveFirst()
      m_recTitles.MoveFirst
      Reload_Members
    End Sub
    

    6 Since we added three new methods to the clsRecordset class, to create an interface for updating data (AddAsNew, SaveChanges and DeleteCurrent), we'll also need to code these routines into our clsTitles class.

    Therefore add the following routines to clsTitles:

    Public Sub clsRecordset_AddAsNew()
    ' Do nothing
    End Sub
    
    Public Sub clsRecordset_SaveChanges()
    ' Do nothing
    End Sub
    
    Public Sub clsRecordset_DeleteCurrent()
    ' Do nothing
    End Sub
    

    Notice that these routines are commented out again. We don't want them to actually do anything, at this stage, since our clsTitles class is just to allow browsing. We still need to implement them, however, since we've committed ourselves to implementing the entire clsRecordset interface within clsTitles.

    7When you're done, try running the application again, and you'll quickly find that it doesn't work. If you're in any doubt, try pressing the First button on our browsing form!

    It's actually very interesting why things aren't working yet. Take a look at the code behind the First button on the browsing form:

    Private Sub cmdFirst_Click()
      Titles.MoveFirst
    End Sub
    

    Visual Basic looks at this code and says "Ok, the programmer wants me to run the MoveFirst method on the Titles object". It then takes a peek at the default interface for the Titles object (which is the clsTitles interface) and sees that it doesn't actually have a MoveFirst method. There's one on the clsRecordset interface, which clsTitles implements, but not on the clsTitles interface itself anymore (we changed it, remember?). The net result: Visual Basic gives you an error.

    As we've already seen, if we need to make calls to code that lives in an implemented interface, then we must prefix the method name with the name of the interface. Therefore, the code above should actually read:

    Private Sub cmdFirst_Click()
      Titles.clsRecordset_MoveFirst
    End Sub
    

    Also prefix the method name with clsRecordset_ in the cmdLast_Click, cmdNext_Click and cmdPrevious_Click routines.

    Okay, let's move on to the Author class of the DataDamage Inc application.

    Try It Out - Adding the Author Class

    1 On the whole, the Author class is very similar to the Titles class, with the simple addition that we'll allow the user to add, edit and delete records. Given the code that we have, this should be pretty easy to do.

    Put a new class module into the application, and copy and paste the entire clsTitles class code into it. Change the name of this new class to clsAuthors.

    Something called containment provides us with a workaround to save us doing this, but more on that later.

    2 Once we have the clsTitles code pasted into our new class, there are a few routines that we obviously need to change: the Authors table in the Biblio database, for example, has a totally different set of fields. I've listed the entire class below, with the relevant changes highlighted:

    Option Explicit
    ' clsAuthors - wraps up the functionality to browse and
    ' maintain the authors table in the Biblio database
    
    Implements clsRecordset
    Public Event DataChanged()
    ' Public members to implement properties
    Public lngAu_ID As Long
    Public strAuthor As String
    Public intYear_Born As Integer
    ' Private members to hold connection to database,
    ' and to the recordset itself
    Private m_recAuthors As Recordset
    Private m_dbDatabase As Database
    ' Class event handlers
    
    Private Sub Class_Initialize()
    ' When an instance of the class is created, we want to
    ' connect to the database and open up the Authors table.
    
    On Error GoTo Class_Initialize_Error
    
    Set m_dbDatabase = Workspaces(0).OpenDatabase _
      (App.Path & "\Biblio.mdb")
      Set m_recAuthors =   m_dbDatabase.OpenRecordset("Authors", _
      dbOpenTable)
    
    Exit Sub
    
    Class_Initialize_Error:
      MsgBox "There was a problem opening either the 
      Biblio " & _
      "database, or the Authors table.", vbExclamation, "Problem"
    Exit Sub
    
    End Sub
    
    Private Sub Class_Terminate()
    ' When the instance of the class is destroyed, we need to
    ' close down the recordset, and also the connection to the
    ' database. Error handling is needed since there could have
    ' been a problem in the Initialize routine making these
    ' connections invalid
    On Error Resume Next
    
    m_recAuthors.Close
    m_dbDatabase.Close
    
    End Sub
    ' Generic Data management methods
    
    Private Sub Reload_Members()
    ' Reloads the member variables (properties) with the field
    ' values of the current record
    On Error GoTo Reload_Members_Error
    
    With m_recAuthors
    
      lngAu_ID = .Fields("au_id")
      strAuthor = "" & .Fields("author")
      If Not IsNull(.Fields("Year Born")) Then
      intYear_Born = .Fields("Year Born")
    Else
      intYear_Born = 0
    End If
    
    End With
    
    RaiseEvent DataChanged
    
    Reload_Members_Error:
      Exit Sub
    End Sub
    
    Private Sub Save_Members()
    ' Assumes that the recordset is in either Edit or Addnew mode.
    On Error GoTo Save_Members_Error
    
    With m_recAuthors
      .Fields("Author") = "" & strAuthor
      .Fields("Year Born") = intYear_Born
    End With
    
    Save_Members_Error:
    
      Exit Sub
    
    End Sub
    
    ' Class methods
    Public Sub clsRecordset_MoveNext()
      If Not m_recAuthors.EOF Then
         m_recAuthors.MoveNext
         Reload_Members
      End If
    End Sub
    
    Public Sub clsRecordset_MovePrevious()
      If Not m_recAuthors.BOF Then
         m_recAuthors.MovePrevious
         Reload_Members
      End If
    End Sub
    
    Public Sub clsRecordset_MoveLast()
      m_recAuthors.MoveLast
      Reload_Members
    End Sub
    
    Public Sub clsRecordset_MoveFirst()
      m_recAuthors.MoveFirst
      Reload_Members
    End Sub
    
    Private Sub clsRecordset_SaveChanges()
    '
    With m_recAuthors
      .Edit
    Save_Members
      .Update
    End With
    
    End Sub
    
    Private Sub clsRecordset_AddAsNew()
    '
      With m_recAuthors
        .AddNew
      Save_Members
        .Update
      End With
    
    End Sub
    
    Private Sub clsRecordset_DeleteCurrent()
    '
      m_recAuthors.Delete
      On Error Resume Next
      m_recAuthors.MoveFirst
      Reload_Members
    
    End Sub
    

    As you can see, it's really pretty similar to the Titles class. The obvious differences are the names of the member properties to get at the field values, as well as the name of the member recordset. Finally, we added some code this time around to handle the Add, Edit and Delete methods, as well as a new private routine, Save_Members, to copy the values of the member variables out to the corresponding fields.

    3 Just as before, we now need to create a browsing and maintenance form. Add a new form to the project and place controls on the form so that it looks like this:

     

    Once again, make the form an MDI child, and name it frmAuthor. Next up, name the two text boxes txtAuthor and txtYearBorn respectively, and the command buttons cmdFirst, cmdPrevious, cmdNext, cmdLast, cmdAdd, cmdUpdate and cmdDelete.

    4 Nowwe're ready to add code. Because of the way we've designed the code so far, the code under this form should be quite familiar to you, and as brief as ever. Take a look at this and add it all to frmAuthor:

    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.clsRecordset_AddAsNew
    End Sub
    
    Private Sub cmdDelete_Click()
      Authors.clsRecordset_DeleteCurrent
    End Sub
    
    Private Sub cmdFirst_Click()
      Authors.clsRecordset_MoveFirst
    End Sub
    
    Private Sub cmdLast_Click()
      Authors.clsRecordset_MoveLast
    End Sub
    
    Private Sub cmdNext_Click()
      Authors.clsRecordset_MoveNext
    End Sub
    
    Private Sub cmdPrevious_Click()
      Authors.clsRecordset_MovePrevious
    End Sub
    
    Private Sub cmdUpdate_Click()
      Update_Authors
      Authors.clsRecordset_SaveChanges
    End Sub
    
    Private Sub Form_Load()
      Set Authors = New clsAuthors
    End Sub
    
    Private Sub Update_Authors()
    ' Updates the members in the Authors object with the data currently
    ' on the form
      Authors.strAuthor = txtAuthor
      Authors.intYear_born = txtYearBorn
    
    End Sub
    

    The big difference between this and the code we have on the Titles form is that this has code to update the data in the class and ultimately the Authors database itself. When the application is running, we can enter data into the form and then click on the Add button, for example. This will trigger the Update_Authors routine to copy the fields out to the class members, and then run the AddAsNew method on the class to copy the information it holds out to a new record. The Update button works in exactly the same way, calling SaveChanges instead of AddAsNew.

    Something for you to consider about this code: the DeleteCurrent routine that we've written will only be successful at deleting Authors that we've entered into the database ourselves. Other Authors in the database have further records in related databases, which prevents the simple deletion of an Author. A nice extension to this code would therefore be an error trapping routine so that our application doesn't crash out with an error message every time this occurs. We discuss error trapping at various points through this book.

    5 Toe To run this full application, you'll need to add some code to the MDI form to load the Author Browser form up.

    Bring up the MDI form now, and click on the Maintain item under the Authors menu heading. Just as before, the code window will come into view for the click event of that menu item. Just add a line of code so that it looks like this:

    Private Sub mnuAMaintain_Click()
      Load frmAuthor
    End Sub
    

    Now run the program, and you'll be able to see both browser forms in action. DataDamage are clearly on their way to a great application.

    The most important point to note, in all this, is that even though we have quite a lot of code in the form, it's all very simple stuff, really easy to understand and follow, and therefore really easy to maintain.

    This is the benefit of the object-oriented design of the application. Although it took some effort at the start to get the code into the clsTitles and clsRecordsets class modules, and then to provide a neat interface to them from the code module, the result is that we have an application that is extremely easy to extend, should the need arise.

    Inheritance and Containment

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

    Summary

    Our time at DataDamage, in this chapter, has taken us over some of most essential object-oriented development issues within Visual Basic. We've been busy with plenty of program code, and we've taken a real-world programming scenario to explore the object-oriented issues.

    Although there's a lot more to come in our journey through object-oriented programming in Visual Basic, we've covered some of the hardest areas already. In particular, we looked at:

  • Class and Object Interfaces
  • The Implements command
  • Multiple interfaces
  • Inheritance
  • Containment and Delegation

    In the next chapter, we'll have a look at a few more design aspects of object-oriented programming as we discuss object hierarchies.

    Why Not Try…

    1In the exercise in this chapter where we created a class to browse the Titles table, what was the purpose of raising the event DataChanged in the Reload_Members procedure?

    2When does the procedure Reload_Members take place in the class?

    3In the exercise in the chapter where we created 'properties' for each field in the Titles table, did you find anything unusual about the way we created the properties?

    4In the exercise in this chapter where we designed a class to browse the Titles table, when is the recordset created?

    5In the second exercise in the chapter, where we 'instantiate' an object based on our class, when and where is the object instantiated?

    6In the second exercise in the chapter, where we 'instantiate' an object based on our class, how do we make use of the event DataChanged raised out of the class, and how is it used?

    7Prove to yourself that the code that was written in exercise 2 is really independent of the data source. Is there any way that you can tell whether the underlying data source for the form is an Access table, an Oracle table, a SQL table, or even a flat text file?

    8On a related note, if we were to change the location of the Titles data from a location on a Local Area Network to a remote Database Server, perhaps in the United Kingdom, what would we need to change in order to accomplish this feat?

    9I want to give you a chance to work with the Implements statement here, so that you can see how the Implements statement allows you to implement interface inheritance. In this exercise, you'll create a class, clsPerson, from which you will later, in our next exercise in fact, implement another class called clsCustomer. Create that new class called clsPerson now, with two properties called Name and Address. Then create a method called ShowInfo (sound familiar?).

    10Now create a new class, clsCustomer, that implements the properties and methods of clsPerson.





  • Home | About | What's New | Source Code | FAQ | Tips & Tricks | Downloads | ToolBox | Tutorials | Game Programming | VB Award | Search | VB Forums | Feedback | VBNews | Copyright & Disclaimer | Advertise | Privacy Policy |

    Quick searches: Site Search | Advanced Site Search 

    Copyright 2002 by Exhedra Solutions, Inc.
    By using this site you agree to its terms and conditions
    VB Explorer and VBExplorer.com are trademarks of Exhedra Solutions, Inc.