Listing Validating Data in Property Let Procedures

Option Explicit

' private class variables to hold property values

Dim mcPrincipalAmount As Currency

Dim mdlnterestRate As Double

Dim mlLoanNumber As Long

Dim mnTerm As Integer

' create an enumeration of loan terms ' set each value equal to the term in months Enum InLoanTerm 1n2Years = 24 1n3Years = 36 1n4Years = 48 1n5Years = 60 1n6Years = 70 End Enum

' Lending limits Private Const MIN_LOAN_AMT Private Const MAX_LOAN_AMT

' Interest rate limits Private Const MIN_INTEREST Private Const MAX_INTEREST_RATE = 0.21

Private Sub C1ass_Initia1ize()

' set default principal amount to 0 mcPrincipalAmount = 0

' set default interest rate to 8% annually mdInterestRate = 0.08 ' set loan number to 0 mlLoanNumber = 0 ' set default term to 36 months mnTerm = 1n3Years End Sub

Public Property Get Principa1Amount() As Currency

PrincipalAmount = mcPrincipalAmount End Property

Public Property Let Principa1Amount(ByVa1 PrincipalAmt As Currency) If PrincipalAmt < MIN_LOAN_AMT Or PrincipalAmt > MAX_LOAN_AMT Then ' don't change value

' raise error

Err.Raise vbObjectError + 1, "Loan Class", _

"Invalid loan amount. Loans must be between " & _ MIN_LOAN_AMT & " and " & MAX_LOAN_AMT & _ " inclusive."

Else mcPrincipalAmount = PrincipalAmt End If End Property

Public Property Get InterestRate() As Double

InterestRate = mdInterestRate End Property

Public Property Let InterestRate(ByVa1 Rate As Double)

If Rate < MIN_INTEREST_RATE Or Rate > MAX_INTEREST_RATE Then ' don't change value ' raise error

Err.Raise vbObjectError + 2, "Loan Class", _

"Invalid interest rate. Rate must be between " & _ MIN_INTEREST_RATE & " and " & MAX_INTEREST_RATE & _ " inclusive."

Else mdInterestRate = Rate End If End Property

Public Property Get LoanNumber() As Long

LoanNumber = mlLoanNumber End Property

Public Property Let LoanNumber(ByVa1 LoanNbr As Long)

mlLoanNumber = LoanNbr End Property

Public Property Get Payment() As Currency

Payment = Application.WorksheetFunction.Pmt _

(mdInterestRate / 12, mnTerm, -mcPrincipalAmount) End Property

Public Property Get Term() As InLoanTerm

Term = mnTerm End Property

Public Property Let Term(ByVa1 Term As InLoanTerm) Select Case Term

Case 1n2Years mnTerm = Term Case 1n3Years mnTerm = Term Case 1n4Years mnTerm = Term Case 1n5Years mnTerm = Term Case 1n6Years mnTerm = Term Case Else

' don't change current value ' raise error

Err.Raise vbObjectError + 3, "Loan Class", _ "Invalid loan term. Use one of the " & _ "InLoanTerm values."

End Select

End Property

Whew! I have a lot to cover on this one. Starting at the top, the first thing I'd like to point out is that I renamed the module-level variables to match the new data types. For example mvInterestRate, originally a Variant, became mdInterestRate (a double). This is the biggest drawback to using naming conventions that indicate a variable's data type. If you change the data type, you either have to change the variable name and then fix it everywhere it appears in your code (Edit ^ Replace can do this trick nicely) or don't change the variable name and live with a variable name that is conveying misinformation. Either way, it's a pain. The moral of the story is choose your data types wisely to begin with if you use a naming convention that reflects the underlying value's data type.

The next interesting aspect of this listing is the lnLoanTerm enumeration. Enumerations provide you with a convenient way to indicate the valid values that a property can assume when you have a property that can assume only a handful of possible values.

End users of your class (you or other developers) will benefit from your use of enumerations because the Auto List Members feature understands enumerations and lists the enumerated choices for them when appropriate, as demonstrated in Figure 11.6.

Although I specified values for each enumeration in this listing, this isn't required. For example, if you wanted a simple list of region choices and didn't care what the value of each choice was, you could create an enumeration such as the following.

Enum rgRegions rgEast rgMidwest rgNorth rgNorthwest rgSouth rgWest End Enum

Figure 11.6

Using enumerations has the added benefit of enabling the Auto List Members feature to provide extra help.

Figure 11.6

Using enumerations has the added benefit of enabling the Auto List Members feature to provide extra help.

When creating enumerations, keep in mind that the enumeration names must be unique compared to any other enumeration names that are in scope. In order to help guarantee unique names, it is helpful to prefix the enumeration with a few letters such as I did for the loan term enumeration (lnLoanTerm).

After I created the lnLoanTerm enumeration in Listing 11.8, I defined a few private constants that represent the minimum and maximum values for loan amount and interest rate. You'll use these in the appropriate Property Let statements to perform some data validation before you accept the incoming value.

As you peruse the various Property procedures, notice that I've modified the Property Get procedures to return a more specific data type that is appropriate for the given property. Likewise, the Property Let procedures now have parameters that use more specific data types.

The PrincipalAmount, InterestRate, and Term Property Let procedures are interesting in that they use the Err object to generate an error. What!? You're writing code to intentionally raise an error? That's right. Keep in mind that when you develop your own classes, you're developing for a different audience—yourself and other programmers. If a procedure passes an invalid value to a property, you need to notify the offending procedure of the error so that it doesn't go on assuming that everything is fine. By raising an error, you give the other procedure a chance to correct the problem in an error-handling routine. Alternatively, the programmer using the object gets the error during the development and testing process and modifies her code to handle invalid values correctly. The syntax of Err.Raise is as follows.

Err.Raise number, [source], [description], [helpfile], [helpcontext]

The parameters of the Raise method are described in the following list.

Number This is a long integer that identifies the error. This should be a value between 513 and 65,535. When using Err.Raise from within a class module, you should use the vbObjectError constant in conjunction with the desired error number.

Source This is an optional string value that you can use to identify the source of the error.

Description This is an optional string that describes the error.

Helpfile This is an optional string that is the fully qualified path to the help file that describes the error.

Helpcontext This is an optional context ID that identifies a topic within the help file that contains information on the error.

My final comment regarding Listing 11.8 relates to the Property Get/Let procedures for the Term property. In order to take advantage of the lnLoanTerm enumeration I set up, you need to specify that the Property Let procedure requires a lnLoanTerm value as a parameter. One benefit of this is that users of your class can take advantage of the Auto List Members feature when they're specifying a new Term value. Of course, once the code is entered, enumerations add to the readability of your code.

When you use enumerations in Property Let procedures, it's possible that an invalid value will be passed to the parameter. Consequently, you need to use a Select Case structure to check the value against the possible enumeration values. Use a final Case Else statement to catch any value that doesn't have an enumeration value defined for it and raise an error or take appropriate action.

This Loan object is a great improvement over your original Loan object. For one thing, when you use the Term property, the Auto List Members feature displays a list of valid choices for you.

Additionally, you have implemented data validation that validates the interest rate, principal amount, and the term. If you pass an invalid value, the Loan object raises an error notifying you of the situation as the following screenshots demonstrate.

Microsoft Visual Basic

Rin-tflfte efrw '-2147221501 (80040003)'! Invalid loan term. Use one of the lnLoanTerm values-

End J | f gebug j|

EJeto

Finally, you've defined the various properties with a data type appropriate for the kind of data that each property will hold.

0 0

Post a comment