Visual Basic Concepts

Using Properties and Collections to Create Object Models

See Also

Objects in a hierarchy are linked together by object properties, that is, properties that return references to objects. An object that contains other objects will have properties that return either references to the objects themselves, or references to collections of objects.

For example, consider a Bicycle object that contains two Wheel objects; each Wheel object might in turn contain a Rim object and a collection of Spoke objects.Figure 6.6 shows a possible object model for the externally creatable Bicycle object and its dependent objects.

Figure 6.6   An externally creatable object with a hierarchy of dependent objects

The Bicycle object would have a Frame property that returned a reference to its Frame object. The Frame object would have a FrontWheel and BackWheel property, each of which would return a Wheel object. The Wheel object would have a Spokes property that would return a Spokes collection object. The Spokes collection would contain the Spoke objects.

You may also have dependent objects that are used internally by classes in your component, and which you do not want to provide to users of your component. You can set the Instancing properties of the class modules that define these objects to Private, so they won’t appear when users browse your type library.

For example, both the Frame and Wheel objects might have collections of Bearing objects, but there may be no reason to expose the Bearings object, or the collections containing it, for manipulation by client applications.

Important   In order to keep the Bearings property from appearing in the type library, you must declare it using the Friend keyword, as described in "Private Communications Between Your Objects," earlier in this chapter.

The Simple Way to Link Dependent Objects

Frequently it makes sense for a complex object to have only one instance of a dependent object. For example, a Bicycle object only needs one Frame object. In this case, you can implement the linkage as a simple property of the complex object:

Private mFrame As Frame

Public Property Get Frame() As Frame
   Set Frame = mFrame
End Property

Private Sub Class_Initialize()
   ' Create the Frame when the Bicycle is initialized.
   Set mFrame = New Frame
End Sub

It’s important to implement such properties as shown above, using a read-only property procedure, rather than simply declaring a public module-level variable, as shown below.

Public Frame As Frame      'Bad idea.

With the second implementation, a user of your component might set the Frame property to Nothing. If there are no other references to the Frame object, it will be destroyed. The effect of this on the Bicycle object is left to the reader’s imagination.

Linking a Fixed Number of Objects

Even when a complex object contains more than one instance of a dependent object, it may make more sense to implement the linkage with properties instead of with a collection. For example, a Bicycle object always has two wheels:

' Create the Wheel objects on demand (As New), instead
'   of in the Bicycle object's Initialize event.
Private mwhlFront As New Wheel
Private mwhlRear As New Wheel

Public Property Get FrontWheel() As Wheel
   Set FrontWheel = mwhlFront
End Property

Public Property Get RearWheel() As Wheel
   Set RearWheel = mwhlRear
End Property

Using Collections in Your Object Model

When the relationship between two objects in a hierarchy is such that the first object contains an indeterminate number of the second, the easiest way to implement the link is with a collection. A collection is an object that contains a set of related objects.

For example, the linkage between the FrontWheel object and its Spoke objects in Figure 6.6 is a collection class. A collection class is a class module that exists solely to group all the objects of another class. In this case, the collection class is named Spokes, the plural of the name of the class of objects it contains. (See "What’s In a Name?", earlier in this chapter, for more information on naming classes.)

Implementing this part of the object model example requires three class modules. From the bottom up, these are:

Dependent Class: Spoke

The Spoke class module is the simplest of the three. It could consist of as little as two Public variables, as in the following code fragment:

' Properties for Spoke
Public PartNumber As Integer
Public Alloy As Integer

The Instancing property of the Spoke class is set to PublicNotCreatable. The only way for a client application to create a Spoke object is with the Add method of the Spokes collection, as discussed in the next section.

Note   This is not a very robust implementation. In practice you would probably implement both of these properties as Property procedures, with code to validate the values that are assigned to them.

For More Information   For details on using Property procedures, see "Programming with Objects," in the Visual Basic Programmer’s Guide.

Dependent Collection Class: Spokes

The Spokes class module is the template for a collection Spoke objects. It contains a Private variable declared as a Collection object:

Private mcolSpokes As Collection

The collection object is created in the Initialize method for the class:

Private Sub Class_Initialize()
   Set mcolSpokes = New Collection
End Sub

The methods of the Spokes class module delegate to the default methods of the Visual Basic Collection object. That is, the actual work is done by the methods of the Collection object. The Spokes class might include the following properties and methods:

' Read-only Count property.
Public Property Get Count() As Integer
   Count = mcolSpokes.Count
End Property

' Add method for creating new Spoke objects.
Public Function Add(ByVal PartNumber As Integer, _
               ByVal Alloy As Integer)
   Dim spkNew As New Spoke
   spkNew.PartNumber = PartNumber
   spkNew.Alloy = Alloy
   mcolSpokes.Add spkNew
   Set Add = spkNew
End Function

As with the Spoke class, the Instancing property of the Spokes class is set to PublicNotCreatable. The only way to get a Spokes collection object is as part of a Wheel object, as shown in the following section describing the Wheel class.

For More Information   See "Object Models" in "Programming with Objects" in the Visual Basic Programmer’s Guide for a discussion of collections, including a more detailed explanation of delegation, a list of methods you need to implement, and instructions for creating a collection that works with For Each.

Externally Creatable Class: Wheel

The Wheel class module has Instancing set to MultiUse, so that any client application can create a Wheel object. The Wheel class module contains a Private variable of the Spokes class:

' Create the Spokes collection object on demand.
Private mSpokes As New Spokes

Public Property Get Spokes() As Spokes
   Set Spokes = mSpokes
End Property

Every Wheel object a client creates will have its own Spokes collection. The collection is protected against accidentally being set to Nothing by making it a read-only property (Property Get). A developer can access the methods and properties of the Spokes collection as shown in the following code fragment:

Dim whl As Wheel
Dim spk As Spoke
Set whl = New Wheel
Set spk = whl.Spokes.Add PartNumber:=3222223, Alloy:=7
' Call a method of the Spoke object.
spk.Adjust
MsgBox whl.Spokes.Count      ' Displays 1 (one item).

The Add method is used to create a new spoke in the Spokes collection of the Wheel object. The Add method returns a reference to the new Spoke object, so that its properties and methods can be called. A spoke can only be created as a member of the Spokes collection.

The difference between the Wheel object, which can be created by any client, and its dependent objects is the value of the Instancing properties of the classes.

For More Information   The Spokes object is created on demand, while the Collection object mcolSpokes was explicitly created. "Programming with Objects" in the Visual Basic Programmer’s Guide discusses the use of As New for creating variables on demand, including performance implications.

Considerations for Linking Objects in an Object Model

Generally speaking, a simpler implementation will be faster. Accessing an item in a collection involves a series of nested references and function calls. Whenever you know that there will always be a fixed number of a dependent object type, you can implement the linkage as a property.

Regardless of how the object model is linked, the key difference between externally creatable objects and dependent objects is the value of the Instancing property of the class module. An object that can be created by other applications will have its Instancing property set to any value except Private or PublicNotCreatable.

All dependent objects, whether they are contained in other dependent objects or in objects that can be created by other applications, will have their Instancing properties set to PublicNotCreatable.

Using Externally Creatable Objects as Dependent Objects

At times you may want to use objects in both ways. That is, you may want the user to be able to create a Widget object independent of the object model, while at the same time providing a Widgets collection as a property of the Mechanism object.

In fact, you may even want to allow the user to create independent instances of the Widgets collection, to move independent Widgets into and out of any Widgets collection, and to copy or move Widgets between collections.

You can make the objects externally creatable by setting the Instancing property of the Widget class and the Widgets collection class to MultiUse.

Important   If the Widget object can be created directly by client applications, you cannot depend on all Widget objects getting initialized by the code in the Add method of the Widgets collection. Objects that will be both creatable and dependent should be designed to require no initialization beyond their Initialize events.

Allowing free movement of Widgets requires implementation of Insert, Copy, and Move methods for your collection. Insert and Move are fairly straightforward, because moving or inserting a reference to an object is as good as moving the object. Implementing Copy, however, requires more work.

This is because client application never actually has the object in its possession. All the client application has is a reference to an object the component has created on its behalf. Thus, when you implement Copy, you must create a duplicate object, including duplicates of any dependent objects it contains.

For More Information   See "Dealing with Circular References" for a discussion of problems that may arise when linking objects together. See "ActiveX Component Standards and Guidelines" for more information on object models.