Visual Basic Concepts

Object Models

Once you've defined a class by creating a class module and giving it properties and methods, you can create any number of objects from that class. How do you keep track of the objects you create?

The simplest way to keep track of objects is to declare an object variable for each object you plan to create. Of course, this places a limit on the number of objects you can create.

You can keep multiple object references in an array or a collection, as discussed in "Creating Arrays of Objects" and "Creating Collections of Objects" earlier in this chapter.

In the beginning, you'll probably locate object variables, arrays, and collections in forms or standard modules, as you do with ordinary variables. As you add more classes, though, you'll probably discover that the objects you're using have clear relationships to each other.

Object Models Express Containment Relationships

Object models give structure to an object-based program. By defining the relationships between the objects you use in your program, an object model organizes your objects in a way that makes programming easier.

Typically, an object model expresses the fact that some objects are "bigger," or more important than others — these objects can be thought of as containing other objects, or as being made up of other objects.

For example, you might create a SmallBusiness object as the core of your program. You might want the SmallBusiness object to have other types of objects associated with it, such as Employee objects and Customer objects. You would probably also want it to contain a Product object. An object model for this program is shown in Figure 9.11.

Figure 9.11   An object model

You can define four class modules, named SmallBusiness, Employee, Customer, and Product, and give them each appropriate properties and methods, but how do you make the connections between objects? You have two tools for this purpose: Object properties and the Collection object. The following code fragment shows one way to implement the hierarchy in Figure 9.11.

' Code for the Declarations section of the
' SmallBusiness class module.
Public Name As String
Public Product As New Product
Public Employees As New Collection
Public Customers As New Collection

The first time you refer to the Product property, the object will be created, because it was declared As New. For example, the following code might create and set the name and price of the SmallBusiness object's Product object.

' Code for a standard module.
Public sbMain As New SmallBusiness
Sub Main
   sbMain.Name = "Velociraptor Enterprises, Inc."
   ' The first time the Product variable is used in
   ' code, the Product object is created.
   sbMain.Product.Name = "Inflatable Velociraptor"
   sbMain.Product.Price = 1.98
   .
   .   ' Code to initialize and show main form.
   .
End Sub

Note   Implementing an object property with public variables is sloppy. You could inadvertently destroy the Product object by setting the property to Nothing somewhere in your code. It's better to create object properties as read-only properties, as shown in the following code fragment.

' Code for a more robust object property. Storage for
' the property is private, so it can't be set to
' Nothing from outside the object.
Private mProduct As New Product

Property Get Product() As Product
   ' The first time this property is called, mProduct
   ' contains Nothing, so Visual Basic will create a
   ' Product object.
   Set Product = mProduct
End If

One-to-Many Object Relationships

Object properties work well when the relationship between objects is one-to-one. It frequently happens, however, that an object of one type contains a number of objects of another type. In the SmallBusiness object model, the Employees property is implemented as a Collection object, so that the SmallBusiness object can contain multiple Employee objects. The following code fragment shows how new Employee objects might be added to this collection.

Public Function NewEmployee(Name, Salary, HireDate, _
ID) As Employee
   Dim empNew As New Employee
   empNew.Name = Name      ' Implicit object creation.
   empNew.Salary = Salary
   empNew.HireDate = HireDate
   ' Add to the collection, using the ID as a key.
   sbMain.Employees.Add empNew, CStr(ID)
   ' Return a reference to the new Employee.
   Set NewEmployee = empNew
End Function

The NewEmployee function can be called as many times as necessary to create employees for the business represented by the SmallBusiness object. The existing employees can be listed at any time by iterating over the Employees collection.

Note   Once again, this is not a very robust implementation. Better practice is to create your own collection classes, and expose them as read-only properties. This is discussed in "Creating Your Own Collection Classes."

Tip   The Class Builder utility, included in the Professional and Enterprise editions of Visual Basic, can generate much of the code you need to implement an object model. Class Builder creates robust object properties and collection classes, and allows you to rearrange your model easily.

Parent Properties

When you have a reference to an object, you can get to the objects it contains by using its object properties and collections. It's also very useful to be able to navigate up the hierarchy, to get to the object that contains the object you have a reference to.

Navigating upward is usually done with Parent properties. The Parent property returns a reference to the object's container. For a discussion of object model navigation, see "Navigating Object Models" in "Programming with Components."

You can find an example of a Parent property in "Adding Properties to Classes" earlier in this chapter.

Tip   When you assign a Parent property to an object in a collection, don't use a reference to the Collection object. The real parent of the object is the object that contains the collection. If the Parent property points to the collection, you'll have to use two levels of indirection to get to the real parent — that is, obj.Parent.Parent instead of obj.Parent.

Parent Properties, Circular References, and Object Teardown

One of the biggest problems with Parent properties is that they create circular references. The "larger" object has a reference to the object it contains, and the contained object has a reference through its Parent property, creating a loop as shown in Figure 9.12.

Figure 9.12   A case of circular references

What's wrong with this picture? The way you get rid of objects when you're done with them is to release all references to them. Assuming the reference to the SmallBusiness object is in a variable named sbMain, as earlier in this topic, you might write the following code:

Set sbMain = Nothing

Unfortunately, there's still a reference to the SmallBusiness object — in fact, there may be many references, because each Employee object's Parent property will hold a reference to the SmallBusiness object.

Since the SmallBusiness object's Employees collection holds a reference to each Employee object, none of the objects ever get destroyed.

TearDown Methods

One solution is to give the SmallBusiness object a TearDown method. This could set all of the SmallBusiness object's object properties to Nothing, and also set all the Collection objects (Employees, Customers) to Nothing.

When a Collection object is destroyed, Visual Basic sets all the object references it was holding to Nothing. If there are no other references to the Employee and Customer objects that were contained in the Employees and Customers collections, they'll be destroyed.

Of course, if the Employee object is made up of finer objects, it will have the same circular reference problem its parent does. In that case, you'll have to give the Employee class a TearDown method. Instead of just setting the Employees Collection object to Nothing, the SmallBusiness object will first have to iterate through the collection, calling the TearDown method of each Employee object.

It's Not Over Yet

Even then, not all the objects may be destroyed. If there are variables anywhere in your program that still contain references to the SmallBusiness object, or to any of the objects it contains, those objects won't be destroyed. Part of the cleanup for your program must be to ensure that all object variables everywhere are set to Nothing.

To test whether this is happening, you may want to add some debugging code to your objects. For example, you can add the following code to a standard module:

' Global debug collection
Public gcolDebug As New Collection

' Global function to give each object a unique ID.
Public Function DebugSerial() As Long
   Static lngSerial As Long
   lngSerial = lngSerial + 1
   DebugSerial = lngSerial
End Function

In each class module, you can put code similar to the following. Each class provides its own name where "Product" appears.

' Storage for the debug ID.
Private mlngDebugID As Long

Property Get DebugID() As Long
   DebugID = mlngDebugID
End Property

Private Sub Class_Initialize()
   mlngDebugID = DebugSerial
   ' Add a string entry to the global collection.
   gcolDebug.Add "Product Initialize; DebugID=" _
   & DebugID, CStr(DebugID)
End Sub

Private Sub Class_Terminate()
   ' Remove the string entry, so you know the object
   ' isn't around any more.
   gcolDebug.Remove CStr(DebugID)
End Sub

As each object is created, it places a string in the global collection; as it's destroyed it removes the string. At any time, you iterate over the global collection to see what objects haven't been destroyed.

For More Information   Object models assume new importance, and a different set of problems, when you use the Professional or Enterprise Edition of Visual Basic to create ActiveX components. See "General Principles of Component Design" in Creating ActiveX Components in the Component Tools Guide.