October 1998

Return to Article Index

Using Scope, Instancing and Enumerations for Better OOP
By by Jeffrey Hasan (Client Server Specialists, Inc.)


Introduction

As developers, most of us understand the importance of scope, which is the "visibility, or "accessibility" that we choose to give to our procedures, variables and constants. Scope is an oft-neglected and surprisingly misunderstood concept for some developers, because they have not thought through what the distinction really implies. At a simple level, scope is distinguished between Public and Private, but the distinction is much more subtle. When leveraged properly, scope is a very powerful tool, because it provides for code that is properly encapsulated. By this, I mean that the procedures, variables and constants in a component should only be made accessible to the parts of your application that need them. A careless use of Public scope for a variable, for example, could have the unintended consequence of exposing the variable to change by parts of the application that should not even be able to access the variable. The issue of scope extends to a discussion of Instancing, which is a class module property setting that defines how objects may be created from your classes. Just as with variables, class modules restrict who sees them and how they may be used. Instancing is critical for setting up object models, and for keeping your code properly encapsulated. The purpose of this article is to examine the issues of scope and instancing, and in particular, to focus on the use of Global scope, and how you can leverage it to your advantage.

Scope in Variables and Procedures

Remember that scope applies not just to variables, but also to procedures and constants, though we’ll start by examining variable scope. The most restricted scope for a variable is when it is private to a procedure, using the Dim keyword. . Please open up a new Standard EXE project, and save it as "MyEXE.vbp", then add a new Module, and set its name to Main. Switch to Form1 and type the following code:
Sub Financial()
Dim sngIntRate As Single
End Sub

Sub Analyze()
End Sub
The variable for interest rate, sngIntRate will only be available to the Financial procedure, and not to the Analyze procedure. If, however, you want the variable to be visible to all procedures in Form1, you would type:
Private sngIntRate As Single

Sub Financial()
End Sub

Sub Analyze()
End Sub
Since "sngIntRate" has private scope, it will only be available for use in this code module. Outside modules in the same project can use the variable if it is scoped as:

Public sngIntRate As Single

Variables should only be scoped Public in one standard module in your project. You should get in the habit of adding a declarations module to your project, which is a standard .BAS module that exclusively holds all the public variable declarations. This is good coding style, because it maintains all the public variables in one place. In our case, Main acts as the declarations module, so this is where the above variable declaration would go.

When it comes to procedures, similar scoping rules apply. Assume that we have added a command button to the form, called "cmdNext", which we want to use to call Financial. Since the form module holds Financial, you can declare it as Private, however, for Financial to be accessible from outside the form module, you must scope it as Public, as in:

Public Sub Financial()
End Sub

Private Sub Analyze()
End Sub
Then you can call Financial from the command button, using:
Private Sub cmdNext_Click()
Call Financial
End Sub
Note that because Analyze is scoped Private, it could be called from Financial but not outside the form module. Although our example shows procedures, bear in mind that the same scoping rules also apply to functions, such that:
Public Function Calculate()
End Sub
would be visible outside its module.

Scope in Constants

Constants improve code readability and maintenance by assigning names to commonly used numbers or strings. Constants may be declared as any data type, for example:
Const INTRATE As Single = 5.5
Const MYNAME as String = "Jeffrey Hasan"
Public constants are available to the entire project, and should be declared in a declarations (.BAS) module. Private constants should be declared in the modules that use them. Constants may not be declared in procedures using the Dim keyword, because this defeats the purpose of using a constant.
Public Const INTRATE As Single = 5.5
is an example of how to declare a public constant. In our example project, this declaration should go only in Main. Later, we will discuss how to encapsulate constants in an enumeration, which exposes constants even further than with the Public keyword.

Scope in Class Modules

Class modules define a blueprint for objects that can be created from the class. Objects expose an interface that allows an outside project to access the components properties and functions. The interface is defined by the scope that you give these properties and procedures. Public property procedures in a class module define properties of the object, while Public procedures are exposed as methods. Objects may also hold procedures for internal use only, so these procedures must be scoped Private.

Before proceeding, please add a new ActiveX DLL project to the project group, and save it as "MyDLL.vbp". Add a new class module to your project and name it clsFinance. It is very important not to skip this step! Your project viewer should now show the following:


Figure 1

Make sure you set a reference to "MyDLL.vbp" from "MyEXE.vbp", using the References dialog box. Next, add the following code to clsFinance:

Private mIntRate As Single
Private mPrincipal As Single

Public Property Get IntRate() As Single
    IntRate = mIntRate
End Property

Public Property Let IntRate(vdata As Single)
    mIntRate = vdata
End Property

Public Property Get Principal() As Single
    Principal = mPrincipal
End Property

Public Property Let Principal(vdata As Single)
    mPrincipal = vdata
End Property

Public Function Calculate()
    Calculate = mPrincipal * mIntRate
End Function
An object created from this class will thus expose 2 properties and one method. The properties are stored internally as local, private variables, such as mIntRate for the IntRate property.

Class modules provide an additional Friend keyword for all procedure types, which allows the procedure to be accessed from anywhere within the project, but not from outside the project. Think of it as a restricted form of Public. Our ActiveX DLL project should not make use of the Friend keyword, because it will be compiled as a component that must expose its properties and methods to the outside world.

Friend Function Calculate()
    Calculate = mPrincipal * (mIntRate/100)
End Function
is an example of how to declare a function as Friend. This function could then be called from anywhere within "MyDLL.vbp", but could not be called from "MyEXE.vbp". Make sure you change the scope back to Public before proceeding!

The Instancing Property

ActiveX DLL components are designed to be referenced by calling applications which need the component’s functionality. The calling application creates objects from the class modules that are contained within the component. The Instancing property settings of the class modules in the component dictate how the calling application is allowed to interact with these class modules. In our example project,"MyEXE.vbp" is the calling application, and "MyDLL.vbp" is the referenced component.

By default, when you add a new class module to an ActiveX DLL, its instancing property is set to MultiUse, which means that the calling application can create several objects from the same class module. For example:

	Dim c, d, e, f As New clsFinance
This code could be placed in the Form_Load() event of Form1, for example, and would dimension 4 objects based on clsFinance. (The alternative SingleUse instancing options won’t be discussed here, because they only apply to out-of-process ActiveX EXE components).

Sometimes you want your component to create objects internally, but not allow a calling application to do the same. This is what Private instancing does. On the other hand, you may want only to restrict the creation privileges, but not the use of the object. PublicNotCreatable instancing means that only your component can create an object, but once the object is created, it is available for use by the calling application.

GlobalMultiUse instancing is where things get very interesting. It is used to create general purpose procedures that are added to the global namespace of the project. These procedures appear in the Object Browser under <globals>, as if they were part of Visual Basic. In addition, these procedures can be used without dimensioning the class first using an object variable.

For example, we currently use our Calculate function by coding the following (place the code in Main and then call Analyze from the Form_Load() event):

Public Sub Analyze()

Dim c As clsFinance
Set c = New clsFinance

c.INTRATE = 5.5
c.Principal = 5500
MsgBox "You will pay $" & c.Calculate & " in interest."

Set c = Nothing

End Sub
However, if we reset the instancing of clsFinance to GlobalMultiUse, then we only need to code the following:
Public Sub Analyze()

INTRATE = 5.5
Principal = 5500
MsgBox "You will pay $" & Calculate & " in interest."

End Sub
Open the Object Browser and confirm for yourself that INTRATE, Principal and Calculate all appear in bold under <globals>.

Bear in mind a few things when using GlobalMultiUse instancing:

You have a wide choice of settings for the Instancing property, and so you should be aware of the implications of each setting. ActiveX DLL components are designed to provide several levels of functionality, so some class modules may be instanced differently from others. In future articles we will look at the important role that Instancing plays in constructing object models.

Using Enumerations

We complete our discussion with the interesting topic of "Enumerations", which are a way of packaging several user defined constants together, usually ones that are related in some way. If we want our example class module, clsFinance, to store a reference interest rate and principal, we could add these constants in the header of the module as:
Public Enum General
    cfinTodaysIntRate = 6
    cfinTodaysPrincipal = 5500
End Enum
"General" is the name of the enumeration, and the two entries are known as its members. Enumerations are added to the component’s type library, and appear in the object browser, under <globals>, even if the instancing of the class module is not set as GlobalMultiUse.

Switching over to "MyEXE.vbp", we can use the enumeration as follows:

MsgBox "Today's interest rate is " & MyDLL.General.cfinTodaysRntRate

The reference to the enumerated constant is coded using the format [libname]. [enumname]. [constantname], where libname is the component name, enumname is the enumeration name, and constantname is the enumeration member. This syntax guarantees that you will reference the correct constant plus allows VB’s IDE to display the choice of constants to you in a floating drop-down box. As a shortcut, you may also use the syntax:

MsgBox "Today's interest rate is " & cfinTodaysIntRate

One of the drawbacks of enumerations is that Visual Basic currently only supports the Long datatype, so that the enumerated values must fall in the range +/- 2,147,483,648. If you want other data types to show up in the component’s type library, then you must use a workaround. Simply set the instancing of the class module to GlobalMultiUse, then add public property get procedures. These will then show up in the type library of "MyDLL", under <globals>. For example, add the following procedure to clsFinance:

Public Property Get NYSE() As String
    NYSE = "New York Stock Exchange"
End Property

Confirm that the instancing is set correctly, then switch over to "MyEXE.vbp". Open up the object browser and confirm that NYSE appears in bold, as a member of the type library of "MyDLL". You can then use NYSE from anywhere within "MyEXE.vbp".

Conclusions

The concepts and techniques we’ve discussed here further the goal of clear, elegant coding, and provide a good set of tools for object oriented programming. The components that you create do not exist in isolation. Instead, every line of code has the potential of being accessed outside the component. It is your responsibility to decide how the procedures may be accessed, and how they appear to the outside world. Understanding scope, instancing and enumerations helps you to do this.


About the Author

Copyright 1995-1998 VB Online. All rights reserved.