The short answer: create a class module. Class modules open the door to
some powerful new ways of structuring programs. The printed manuals and
online help are the best resources to study for all the intricacies of class modules
and the objects you can create with them. Here I'll present a simple example,
just enough to whet your appetite and provide a framework for learning.
Throughout this book, you'll find numerous working examples of class
modulesthey provide a great way to create structured code, and I like using them.
LoanA Class Module Example
To demonstrate many of the most important features of objects, I've built a relatively simple class module that lets you create Loan objects. The Loan object is used to calculate the payment schedule over the life of a loan. You could easily add more methods and properties to this class or restructure the way it works, but I kept it fairly simple on purpose, to provide a working model for you to study.
To create a class module, start a new Standard EXE project, choose Add Class Module from the Project menu, and double-click the Class Module icon in the Add Class Module dialog box. Add the following code, change the Name property to Loan, and save the module as LOAN.CLS. The code for the Loan class is followed by explanations of the different parts of the code.
`LOAN.CLS - This is a class module that provides a
`blueprint for creating Loan objects
Option Explicit
`This variable is known only within this class module
Private mintMonths As Integer `Number of months of loan
`~~~Property (R/W): Principal
Public Principal As Currency
`~~~Property (R/W): AnnualInterestRate
Public AnnualInterestRate As Single
`~~~Property (R/W): Months
`Lets user assign a value to Months property
Property Let Months(intMonths As Integer)
mintMonths = intMonths
End Property
`Gets current value of Months property for user
Property Get Months() As Integer
Months = mintMonths
End Property
`~~~Property (R/W): Years
`Lets user assign a value to Years property
Property Let Years(intYears As Integer)
mintMonths = intYears * 12
End Property
`Gets current value of Years property for user
Property Get Years() As Integer
Years = Round(mintMonths / 12#, 0)
End Property
`~~~Property (R/O): Payment
`Gets calculated Payment for user
Property Get Payment() As Currency
Dim sngMonthlyInterestRate As Single
`Verify that all properties are loaded
If PropertiesAreLoaded() Then
sngMonthlyInterestRate = AnnualInterestRate / 1200
Payment = (-sngMonthlyInterestRate * Principal) / _
((sngMonthlyInterestRate + 1) ^ (-mintMonths) - 1)
Else
Payment = 0
End If
End Property
`~~~Property (R/O): Balance()
`Gets array of loan balances
Property Get Balance() As Currency()
Dim intCount As Integer
Dim curPayment As Currency
ReDim curBalance(0) As Currency
If PropertiesAreLoaded() Then
ReDim curBalance(mintMonths)
curBalance(0) = Principal
curPayment = Round(Payment, 2) `Rounds to nearest penny
For intCount = 1 To mintMonths
curBalance(intCount) = curBalance(intCount - 1) * _
(1 + AnnualInterestRate / 1200)
curBalance(intCount) = curBalance(intCount) - curPayment
curBalance(intCount) = Round(curBalance(intCount), 2)
Next intCount
End If
Balance = curBalance
End Property
`~~~Method: Reset
`Initializes all properties to start over
Public Sub Reset()
Principal = 0
AnnualInterestRate = 0
mintMonths = 0
End Sub
`Private function to check if all properties are properly loaded
Private Function PropertiesAreLoaded() As Boolean
If Principal > 0 And AnnualInterestRate > 0 And mintMonths > 0 Then
PropertiesAreLoaded = True
End If
End Function
The Loan objects created in the main program will have properties and methods, just like other objects. The simplest way to create readable and writable properties is to declare public variables. Principal and AnnualInterestRate, declared at the top of the LOAN.CLS listing, are two such properties:
Public Principal As Currency Public AnnualInterestRate As Single
One variable, mintMonths, is declared Private, as shown below, so that the outside world will be unaware of its existence. Within the Loan object, you can be assured that the value contained in this variable is always under direct control of the object's internal code.
Private mintMonths As Integer `Number of months of loan
NOTE
You might wonder about the mint prefix to this variable's name. The m part indicates this variable is declared at the module level, and the int part indicates the variable is of type Integer. You also might be wondering why the public variables don't have any Hungarian Notation prefixes. This is because public variables behave like properties, and the accepted standard for properties exposed to the outside world is avoidance of prefixes. This makes sense when you realize the purpose of variable prefixes is to improve the readability of the internal code of a class or other code module. Properties, on the other hand, are exposed to the external world, where clarity of naming matters more than understanding the underlying code.
The variable mintMonths stores the number of months of the loan. I could have made this a simple public variable, but I've designed the Loan object with two related public properties, Years and Months, either of which can be set by the user to define the length of the loan. These properties then internally set the value of mintMonths. Read on to see how these two properties are set up to do this.
The Property Let statement provides a way to create a property in your object that can take action when the user assigns a value to it. Simple properties, such as Principal, just sit there and accept whatever value the user assigns. The Months property, on the other hand, is defined in such a way that you can add code that will be executed whenever a value is assigned to the property. In this case, the code is a simple assignment of the number of months to the local mintMonths variable for safekeeping:
Property Let Months(intMonths As Integer)
mintMonths = intMonths
End Property
The Property Let and Property Get statements work hand in hand to build one writable and readable property of an object. Here the Property Get Months statement provides a way for the user to access the current contents of the Months property:
Property Get Months() As Integer
Months = mintMonths
End Property
Stop and think about what I've just done here. From the outside world, the Months property appears to be a simple variable that can store values assigned to it, and it supplies the same value when the property is accessed. But when you look at the implementation of the Months property from a viewpoint inside the Loan class module, you see a lot more going on. Months is not just a simple variable. Code is activated when values are written to or read from the property, and it's possible for this code to take just about any action imaginable. It's a powerful concept!
If you don't add a corresponding Property Get statement to go along with a Property Let statement, the defined property will be write-only. Conversely, a Property Get without a corresponding Property Let results in a read-only property. For some properties, this can be a good thing. This is true for the Payment property, which is described later in this chapter.
The Years property, shown below, provides a second, alternative, property that the user can set to define the length of the loan. Notice that the value set into the Years property is multiplied by 12 internally to convert it to months. This detail is hidden from the user.
Property Let Years(intYears As Integer)
mintMonths = intYears * 12
End Property
Property Get Years() As Integer
Years = Round(mintMonths / 12#, 0)
End Property
NOTE
The Round function is new in Visual Basic 6. I've used it in the Property Get Years procedure to round off the calculated number of years to zero digits after the decimal point, or to the nearest whole year.
Compare the Years and Months property procedures to see why I created the local variable mintMonths to store the actual length of the loan separately from the properties used to set this value. Again, the implementation of the Years and Months properties is encapsulated within the Loan object, and the details of how these properties do their thing is hidden from the outside world.
When the user accesses the value of the Loan object's Payment property, shown below, a complicated calculation is triggered, and the payment amount is computed from the current value of the other property settings. This is a clear example that shows why Property Let and Property Get statements add useful capabilities beyond those provided by properties that are created by simply declaring a public variable in a class module.
Property Get Payment() As Currency
Dim sngMonthlyInterestRate As Single
`Verify that all properties are loaded
If PropertiesAreLoaded() Then
sngMonthlyInterestRate = AnnualInterestRate / 1200
Payment = (-sngMonthlyInterestRate * Principal) / _
((sngMonthlyInterestRate + 1) ^ (-mintMonths) - 1)
Else
Payment = 0
End If
End Property
The Balance property, which is set up as a read-only property because I define it only in a Property Get statement with no corresponding Property Let, is shown next.
Property Get Balance() As Currency()
Dim intCount As Integer
Dim curPayment As Currency
ReDim curBalance(0) As Currency
If PropertiesAreLoaded() Then
ReDim curBalance(mintMonths)
curBalance(0) = Principal
curPayment = Round(Payment, 2) `Rounds to nearest penny
For intCount = 1 To mintMonths
curBalance(intCount) = curBalance(intCount - 1) * _
(1 + AnnualInterestRate / 1200)
curBalance(intCount) = curBalance(intCount) - curPayment
curBalance(intCount) = Round(curBalance(intCount), 2)
Next intCount
End If
Balance = curBalance
End Property
The Balance property demonstrates two new features of Visual Basic. In addition to the new Round function, which is used several places throughout the Loan class, the Balance property returns an entire array of numbers rather than just a single value. Take a close look at the Property Get declaration for this property. The return type is stated As Currency(). The parentheses indicate that an array is to be returned. In the Balance property's code, I declared a local array of type Currency, called curBalance, and after expanding and filling this dynamic array with values, assigned it to Balance, the name of the property. Voila! The whole array of monthly loan balances is returned to the calling application.
If anything goes wrong hereif the user hasn't set all the pertinent properties first, for examplethe property will return an array dimensioned to 0, with the value 0 in it. This is a simplified way to handle potential errors. My intention is not to cover the best ways to handle all potential errors here but simply to show you an example class object in a simplified, straightforward manner.
Methods, like properties, are an important aspect of the interface an object presents to the outside world. I've added one simple method to the Loan object so that you can get a feel for how methods work. The Reset method is simply a Sub procedure designed to erase the loan's property values. You don't need to call this method unless multiple loan calculations are to be made using the same Loan object.
Public Sub Reset()
Principal = 0
AnnualInterestRate = 0
mintMonths = 0
End Sub
NOTE
The Class_Initialize and Class_Terminate event procedures provide a way to automatically initialize variables and perform housekeeping at the time an object is created and when it is destroyed.
Finally, I added the PropertiesAreLoaded private function to show how procedures that are not known to the outside world can be added to an object. These private procedures are called only from other code within a class. In this case, since I needed to check in more than one place to see whether the object's properties were loaded and ready to go, all the code required to check all the properties is contained in one utility procedure.
Private Function PropertiesAreLoaded() As Boolean
If Principal > 0 And AnnualInterestRate > 0 And mintMonths > 0 Then
PropertiesAreLoaded = True
End If
End Function
NOTE
If you don't explicitly assign a return value to a Function procedure, the procedure will return the default value for its type. For example, by default, Variant functions return Empty, Integer functions return the value 0, and Boolean functions return False. That's why we allow the PropertiesAreLoaded function to appear to return nothing when the test is False.
SEE ALSO
- The next section, "Dear John, How Do I... Use My New Object?" for a demonstration of how to use objects