Microsoft Office 2000/Visual Basic Programmer's Guide |
One way to design an interface is to create a class that describes Sub, Function, and Property procedure definitions, including arguments and return types, without adding any code to the procedures. This is referred to as an abstract interface. The advantage to implementing an abstract interface is that the class that implements it can include whatever code you want in the procedures it provides. Two classes that implement the same method can contain entirely different code within that method.
The sample file Library.xls, found in the ODETools\V9\Samples\OPG\Samples\CH09 subfolder on the Office 2000 Developer CD-ROM, implements an abstract interface. The solution models a simple library system, which adds and catalogs different library holdings, and checks them out to patrons. There are four custom classes that represent different library items: Fiction, Nonfiction, Periodical, and Reference. The project also contains a class module named LibraryItem, which provides an abstract interface to the other classes. You may want to study and run this example while reading this section.
The four different objects that the library offers are all similar, but not identical. Fiction and Nonfiction objects can be checked out, while Periodical and Reference objects can't. But the objects are similar enough to share certain common properties. And as the library expands and adds new types of holdings, such as Audio or Film, those new holdings will also have common properties. All of these objects implement the LibraryItem interface, which supplies the common properties that each object needs to have. Within each object's class module, a property procedure can contain custom code tailored to that object's needs.
The interface described by the LibraryItem class defines four read-write properties: Name, ItemType, AllowCheckOut, and CheckedOut. The Name property is a simple public module-level variable; the others are paired Property Let and Property Get procedures. Because this is an abstract interface, none of the Property procedures contains any code.
The LibraryItem class also contains a public enumeration that defines numeric constants that represent each of the four existing object types. In the interest of organization, the enumeration appears in the LibraryItem class; however, it could also be defined in a standard module. The enumeration itself does not form part of the interface.
The Fiction, Nonfiction, Periodical, and Reference objects all implement the LibraryItem interface, so each of them has its own property procedures for each property defined by the interface. You can customize these procedures according to the needs of the object. For example, the AllowCheckOut property indicates whether a particular item can be checked out. For Fiction and Nonfiction objects, this property should return True. For Periodical and Reference objects, it should return False. Compare the LibraryItem_AllowCheckOut property procedures for the Fiction and Periodical classes:
' AllowCheckOut property for Fiction object.
Private Property Get LibraryItem_AllowCheckOut() As Boolean
' Fiction can be checked out.
LibraryItem_AllowCheckOut = True
End Property
' AllowCheckOut property for Periodical object.
Private Property Get LibraryItem_AllowCheckOut() As Boolean
' Don't allow periodical to be checked out.
LibraryItem_AllowCheckOut = False
End Property
Although it's practical for both Fiction and Periodical objects to have an AllowCheckOut property, it's not practical for both properties to return the same value.
The real power of implementing an abstract class becomes apparent when you begin using the objects to construct an application. The library system needs to be designed to handle any type of holding, and yet to distinguish between different types at the same time. Since the LibraryItem class is implemented in all four of the specific classes, an item of type LibraryItem
can represent any of those objects, just as a Control object can represent any type of control generically.
The concepts of implementing an abstract class are more important than the details of the library solution, so the discussion here will focus on explaining the concepts. When the user clicks the Catalog New Items button on the switchboard form (frmLibrary), another form loads (frmCatalog). This form allows the user to enter a name for a new library holding and select the type of holding from a combo box. When the user clicks the Catalog button, a new object of the appropriate type is created and added to the collection.
Here's the code that creates a new library item in the cmdCatalog_Click event procedure. Note that this code fragment calls the AddLibraryItem procedure, passing in the name of the library item and its type, and returns a reference to an object that is assigned to a variable of type LibraryItem
:
Dim colItem As LibraryItem
Set colItem = AddLibraryItem(Me.txtItemName, Me.cboItemType.ListIndex)
Next, take a look at the AddLibraryItem procedure. This procedure examines the value passed in for the lngType argument and creates a new object of type Reference
, Fiction
, Nonfiction
, or Periodical
. It then adds this reference to a global VBA Collection object in order to store it. Finally, the procedure returns a reference to the new object. Since the procedure's return type is declared as type LibraryItem
, it is this type of object that is returned to the calling procedure and assigned to a variable of type LibraryItem
:
Public Function AddLibraryItem(strName As String, _
lngType As opgItemType) As LibraryItem
' This function creates a library item of the specified type,
' sets its Name property, adds it to the collection,
' and returns a reference to the object.
' Note that a variable of type LibraryItem is used to store the
' reference, even though the code creates a specific object type.
' In a more sophisticated solution, you would probably include
' this method in a collection class as the Add method.
Dim colNew As LibraryItem
' Determine what type of library item this is and
' create a new object of the appropriate type.
Select Case lngType
Case opgItemType.ITEM_REFERENCE
Set colNew = New Reference
Case opgItemType.ITEM_FICTION
Set colNew = New Fiction
Case opgItemType.ITEM_NONFICTION
Set colNew = New NonFiction
Case opgItemType.ITEM_PERIODICAL
Set colNew = New Periodical
End Select
' Set object's Name property.
colNew.Name = strName
' Add new object to collection, specifying its name as
' the key for the item. The key can be used to retrieve
' the item from the collection.
gcolLibItems.Add colNew, strName
' Return reference to new object.
Set AddLibraryItem = colNew
End Function
As you can see from this procedure, implementing the LibraryItem interface in the Reference, Fiction, Nonfiction, and Periodical classes means that a reference to any of those objects can be assigned to a variable of type LibraryItem
. You don't need to know ahead of time exactly which object your code will be dealing with. When you set or return a property on the LibraryItem object, the property procedure for the appropriate object runs.
For example, the following event procedure determines whether an item can be checked out, and if so checks it out by setting the CheckedOut property to True. All of the available object types have an AllowCheckOut property. For Fiction and Nonfiction objects, this property returns True. For Periodical and Reference objects, it returns False. Without knowing exactly which type of object the colLibItem
variable refers to, you can check the property and respond appropriately:
Private Sub cmdCheckOut_Click()
' Attempt to check out library item.
Dim colLibItem As LibraryItem
Dim strMsg As String
' Locate item in collection and return reference to it.
' Assign reference to variable of type LibraryItem.
' Note that although the object has a specific type,
' you don't need to know what it is to use it.
' Value of selected text in combo box is same as
' item's key in collection. The key can be used to retrieve
' the item from the collection.
Set colLibItem = gcolLibItems.Item(cboItems.Text)
' Check whether this item can be checked out.
If colLibItem.AllowCheckOut Then
If colLibItem.CheckedOut Then
' If item is already checked out, apologize.
strMsg = "Sorry, this item is already checked out."
Else
' Otherwise, check out and give return date.
colLibItem.CheckedOut = True
strMsg = "Item is checked out to you; due back " & Date + 14
End If
Else
' If item can't be checked out, apologize.
strMsg = "Sorry, this item can't be checked out."
End If
' Display message.
MsgBox strMsg
End Sub
You can also add properties or methods that are not part of the interface to any object. For example, you could add a Volume property to the Periodical and Reference classes to set and return the volume number for the object. To set that property, however, you need to use an object of type Periodical
or Reference
, because the Volume property is not part of the LibraryItem interface. To determine whether the type of object you're working with is a Periodical or Reference object, you can use the If TypeOf construct. For example, the following procedure sets the volume number if the object passed in is a Periodical or Reference object:
Public Sub SetVolume(colItem As LibraryItem, _
strVolNum As String)
' Declare object variables of specific types.
Dim perItem As Periodical
Dim refItem As Reference
' Use Is TypeOf... construct to check whether
' library item is a periodical.
If TypeOf colItem Is Periodical Then
' Assign library item reference to object variable
' of type Periodical.
Set perItem = colItem
' Set Volume property on Periodical object.
perItem.Volume = strVolNum
Else
' Check whether library item is reference material.
If TypeOf colItem Is Reference Then
' Assign library item reference to object variable
' of type Reference.
Set refItem = colItem
' Set Volume property on Reference object.
refItem.Volume = strVolNum
Else
' Library item must be fiction or nonfiction.
MsgBox "You can't enter a volume label for " _
& "a fiction or nonfiction item."
End If
End If
End Sub
Note You could also implement the Volume property shown in the previous example as part of the LibraryItem interface, and simply not add any code to it for the Fiction and Nonfiction classes. This may be a better approach, because then you don't need to use the If TypeOf construct or create an additional object variable.