Visual Basic Banner Exchange

July 1998

Return to Article Index

Smart Object Management using Interfaces and Polymorphism
By Jeffrey Hasan (Client Server Specialists, Inc.)


Introduction

If you’ve managed to get through the title of this article, then you’ve taken your first step towards understanding concepts that strike fear into the hearts of most novice and intermediate VB programmers. Objects are the stuff that modern applications are made of, and as such, smart object management is not an option, but a necessity. Understanding how Visual Basic manages objects should be the first goal of any serious developer.

Now let’s start by tackling an oft-quoted criticism of VB, namely, that: "Visual Basic is not a true object-oriented language." Critics of VB are quick to point out that the language does not support the direct inheritance of object properties between related objects. This is true, however, VB does provide a substitute method for inheritance, known as "interface inheritance" that accomplishes most of the same goals. Unfortunately, until you understand what an interface is, you won’t appreciate the distinction. (Though you will after reading this article, so read on!) Visual Basic provides the sophisticated developer with many tools for smart object management. And with every new release of VB, your object management toolkit becomes bigger, and thereby the language becomes more "object-oriented". So instead of listening to the critics, focus on understanding the large number of object-oriented features that VB does have, as opposed to those it does not.

This is the first in a series of articles that I will write to examine object management with Visual Basic. In order to appreciate the concepts presented here, you should, at a minimum, understand how to code standard class modules; and how to define an object and set and retrieve its properties. Read on, and as usual, prepare to impress your friends!

What is an Interface?

If you’ve ever coded a class module, then you’ve created an interface. An object’s interface is its means of communicating with the outside world. Interfaces allow objects to encapsulate their code and to regulate the inflow and outflow of data and results through well-defined channels. The interface is defined by the object’s properties, methods and events, and are coded within class modules as property procedures, standard procedures, and event procedures, respectively. Although the code within an object may change over time, its interface should not change. The outside world expects to communicate with in an object in a given way, regardless of what happens internal to the object.

The interface that is defined in a standard class module will be referred to from here on out as the "default interface". In addition to their default interface, objects may use implemented interfaces, that is, interfaces that have been defined in a separate class module. This is useful because sometimes different objects employ the same interface. It makes sense to define the interface just once, then to implement it multiple times, as needed.

The Audio-Visual Objects Example

Consider a TV and a VCR. We can come up with several common properties to describe each, such as the manufacturer and the model number. In addition, each object has unique properties. TVs, for example, have a screen size, while VCRs do not. And VCRs have 2 or 4 tape heads, while this property does not apply to TVs, which always have none. Each object and their properties are shown in the schematic below:

TV Object VCR Object
Manufacturer Manufacturer
Model Number Model Number
Screen Size Number of Heads
Description Description

In defining the interface for each object, it is apparent that the "Manufacturer", "Model Number" and "Description" properties are common to each object. The "Description" property is a read-only string that provides a summary description of the TV or VCR. These properties will be coded once in a "stand-alone" interface that will then be implemented in each object. The properties that are unique to each object will be coded within each object’s default interface.

Defining and Implementing an Interface

Coding an interface in Visual Basic is easy, and must be done in a standard class module. We will work with the "audio-visual objects" presented above. Start by opening a new, "Standard EXE" project, and add a new class module, which you should name "IAudioVisual", and save as "IAudioVisual.cls". Add the following code to the class:
'Class Name:    IAudioVisual
'Author:        Jeffrey Hasan, CSSI
'Date:          June 14, 1998
'Description:   Defines an interface for Audio-Visual objects

Option Explicit

Public Property Let Manufacturer(ByVal vdata As String)

End Property

Public Property Get Manufacturer() As String

End Property

Public Property Let ModelNumber(ByVal vdata As String)

End Property

Public Property Get ModelNumber() As String

End Property

Public Property Get Description() As String

End Property
Defining an interface involves nothing more than defining empty property procedures. You see above Property Let/Get procedures for "Manufacturer", "Model Number", and "Description".

Now let’s code classes for our TV and VCR objects. We will require a separate class for each object. I should note here that as you study my example you will doubtless find more efficient ways of coding this functionality. Bear with me, these examples are here to present difficult concepts in as clear a manner as I know how.

Add a new class module to the project and name it "CTV". Add the following code:

'Class Name:    CTV
'Author:        Jeffrey Hasan, CSSI
'Date:          June 14, 1998
'Description:   Defines properties for a TV Audio-Visual objects

Option Explicit

Implements IAudioVisual

'Local variables to hold property value(s)
Private mvarScreenSize As Integer

'Local variables to hold Implemented properties
Private mvarManufacturer As String
Private mvarModelNumber As String

'*** Default Interface ***
Public Property Let ScreenSize(ByVal vdata As Integer)
    mvarScreenSize = vdata
End Property

Public Property Get ScreenSize() As Integer
    ScreenSize = mvarScreenSize
End Property

So far, this class defines property procedures for screen size, and also provides private variables for the local storage of the object’s properties, including the manufacturer, model number and screen size. Externally-defined interfaces are assigned to an class using the "Implements" keyword, which is below the "Option Explicit" statement in the code above. In this case, we write:

Implements IAudioVisual

Where "IAudioVisual" is the name of the class that defines the implemented interface. Now go to the upper left object drop down box in the IDE, and select the "IAudioVisual" entry. In the right drop down box you will now see its property procedures. Select each one in turn to add it to the CTV class.

Since we haven’t yet defined any code for the "Manufacturer" and "Model Number" methods, we have to do it here. Add the following code to the CTV class:

'*** Implemented Interface ***
Private Property Let IAudioVisual_Manufacturer(ByVal RHS As String)
    mvarManufacturer = RHS
End Property

Private Property Get IAudioVisual_Manufacturer() As String
    IAudioVisual_Manufacturer = mvarManufacturer
End Property

Private Property Let IAudioVisual_ModelNumber(ByVal RHS As String)
    mvarModelNumber = RHS
End Property

Private Property Get IAudioVisual_ModelNumber() As String
    IAudioVisual_ModelNumber = mvarModelNumber
End Property

Private Property Get IAudioVisual_Description() As String
    IAudioVisual_Description = "The " & mvarManufacturer _
        & " " & mvarModelNumber & " TV has a(n) " _
        & mvarScreenSize & " inch screen."
End Property
The "Description" property as you see here is a read-only string that describes the objects using its assigned properties. Now let’s add a third class module to the project, and name it "CVCR". Place the following code in the class:
'Class Name:    CVCR
'Author:        Jeffrey Hasan, CSSI
'Date:          June 14, 1998
'Description:   Defines properties for a VCR Audio-Visual objects

Option Explicit

Implements IAudioVisual

'Local variables to hold property value(s)
Private mvarNumber_Heads As Integer

'Local variables to hold Implemented properties
Private mvarManufacturer As String
Private mvarModelNumber As String

'*** Default Interface ***
Public Property Let Number_Heads(ByVal vdata As String)
    mvarNumber_Heads = vdata
End Property

Public Property Get Number_Heads() As String
    Number_Heads = mvarNumber_Heads
End Property

'*** Implemented Interface ***
Private Property Let IAudioVisual_Manufacturer(ByVal RHS As String)
    mvarManufacturer = RHS
End Property

Private Property Get IAudioVisual_Manufacturer() As String
    IAudioVisual_Manufacturer = mvarManufacturer
End Property

Private Property Let IAudioVisual_ModelNumber(ByVal RHS As String)
    mvarModelNumber = RHS
End Property

Private Property Get IAudioVisual_ModelNumber() As String
    IAudioVisual_ModelNumber = mvarModelNumber
End Property

Private Property Get IAudioVisual_Description() As String
    IAudioVisual_Description = "The " & mvarManufacturer _
        & " " & mvarModelNumber & " VCR has " _
        & mvarNumber_Heads & " video heads."
End Property
Note that every property of the external interface must be implemented, or an error will be generated when you try to compile the project. There is a way to "turn off" selected parts of the interface, but that will be left for another article.

Using the Implemented Interface

Try not to get distracted by the fact that the CTV and CVCR class modules have both default and implemented interfaces. Instead, view the interface as a seamless whole where each property is just like any other, regardless of how it’s defined.

Now let’s start creating objects from our classes so you can see our interfaces in action. Switch to the form, and add the following code to the top of the form:

'Form Name:     Form1
'Author:        Jeffrey Hasan, CSSI
'Date:          June 14, 1998
'Description:   Form1 module

Option Explicit

'Declare object variables
Private m_TV As CTV
Private m_VCR As CVCR

'Declare interface variable
Private m_IAudioVisual As IAudioVisual

All we’ve done is to declare three object variables: one for the TV object, one for the VCR 
object, and one for the "IAudioVisual" interface.  Now add the following code to 
the "Form_Load" event:

Private Sub Form_Load()

Set m_TV = New CTV

'Assign the interface to the m_TV object
Set m_IAudioVisual = m_TV

'Set TV properties
m_IAudioVisual.Manufacturer = "Panasonic"
m_IAudioVisual.ModelNumber = "CT-20S15R"
m_TV.ScreenSize = 18

'Generate a description of the TV
MsgBox m_IAudioVisual.Description, vbExclamation, _
    "TV Description"

‘------------------------------

Set m_VCR = New CVCR

'Assign the interface to the m_VCR object
Set m_IAudioVisual = m_VCR

'Set VCR properties
m_IAudioVisual.Manufacturer = "Hitachi"
m_IAudioVisual.ModelNumber = "M161"
m_VCR.Number_Heads = 2

'Generate a description of the VCR
MsgBox m_IAudioVisual.Description, vbExclamation, _
    "VCR Description"

End

End Sub
Look at the code above the dotted line. Here are the steps of what we’re doing:

The VCR object is coded the same way. Now run the project, and you should see the following 2 dialog boxes:

Using Polymorphism for Efficiency

"Polymorphism" takes interfaces a step further (in the right direction!) Polymorphism is the ability to communicate with different objects using the same implemented interface. Think about the example above: the TV and VCR objects implement the same "IAudioVisual" interface, so wouldn’t it make sense to write the code behind the interface just once? It does, and you’ll find that polymorphism leverages existing code and reduces your code-writing burden.

Add a new, standard module to your project, then add the following code:

'Module Name:   Module1
'Author:        Jeffrey Hasan, CSSI
'Date:          June 14, 1998
'Description:   Module1 module

'Declare object variables
Private m_TV As CTV
Private m_VCR As CVCR

Option Explicit

Private Sub Manufacturer(obj As IAudioVisual, vdata As String)
    obj.Manufacturer = vdata
End Sub

Private Sub ModelNumber(obj As IAudioVisual, vdata As String)
    obj.ModelNumber = vdata
End Sub

Private Sub Description(obj As IAudioVisual)
    Dim MyObject As String
    MyObject = TypeName(obj)
    MyObject = Mid(MyObject, 2, Len(MyObject))
    MsgBox obj.Description, vbExclamation, MyObject & " Description"

End Sub
This code shows polymorphism in action (although the code listing is still incomplete). I’ve coded 3 very generalized procedures for each of the 3 implemented interface properties. The "Manufacturer" procedure, for example, accepts either a TV or VCR object and a string, then sets the "Manufacturer" property of the passed object equal to the passed string. One procedure handles both types of object. It can do this because each object implements the same interface.

Now let’s add the rest of the code. Add 2 object variable declarations and a "Sub Main" procedure to the standard module, as shown below:

Public Sub Main()

'Method: Declare procedures with the interface as the argument

Set m_TV = New CTV
Manufacturer m_TV, "Panasonic"
ModelNumber m_TV, "CT-20S15R"
m_TV.ScreenSize = 18

'Generate a description of the TV
Description m_TV

Set m_VCR = New CVCR
Manufacturer m_VCR, "Hitachi"
ModelNumber m_VCR, "M161"
m_VCR.Number_Heads = 2

'Generate a description of the VCR
Description m_VCR

End Sub
You see that the implemented properties are set through calls to our procedures, eg - Manufacturer m_TV, "Panasonic", where "Manufacturer" is the procedure, and "m_TV" and "Panasonic" are the required parameters. The default interface properties are still set by direct reference to the object, eg, "m_TV.ScreenSize = 18".

Recall earlier, when I said that setting an interface variable equal to an object variable simply assigns the interface to that object. The same is true of the procedures above, which appear to expect the passed object to be of type "IAudioVisual". In actual fact, the procedure is actually expecting an object that implements the "IAudioVisual" interface, not the actual interface itself. To prove this, the TypeName() function in the "Description" procedure returns either "CTV" or "CVCR", never "IAudioVisual". Similarly, the following code:

'Declare object variables
Private m_VCR As CVCR

'Declare interface variable
Private m_IAudioVisual As IAudioVisual

Set m_VCR = New CVCR

'Assign the interface to the m_VCR object 
Set m_IAudioVisual = m_VCR

Msgbox TypeName(m_IAudioVisual)

returns "CVCR" as the Type Name of the "m_IAudioVisual" variable.

Conclusions

Concepts like interfaces and polymorphism are complicated only if you make them so. Once you start thinking in objects (instead of procedures, as most of us are used to) you will quickly become comfortable with thinking about how objects use their interfaces to communicate with each other. It is often written that interfaces are like a contract between the object and the outside world. Polymorphism is a natural extension of thinking about the similarities that diverse objects have. Just as you would code a frequently used function into a public function procedure, it makes sense to define a common interface, and to have a central (code) location for handling common interface requests.


About the Author

Copyright 1995-1998 VB Online. All rights reserved.