I've been writing some LotusScript classes recently, and it occurred to me to write about this technique for improving performance -- hope it's not too obvious.
Frequently, classes have some properties you're not sure the caller will use. A lot of things, you would initialize in the constructor ("Sub New"), so that they will be available when needed. But others, which might be expensive to calculate, you'd like to delay calculating them until someone asks for them (the computer version of "slack"). But, if someone asks for them twice, you don't want to do the expensive operation twice.
The way I've been doing this, is to set up a "Property Get" for the item, and have a private member where the value is stored the first time it's calculated. For instance:
Const ERR_HM_NOPADDOCK = 18651
Const PADDOCK_VIEW_NAME = "PaddocksByTitle"
Class HorseManager
m_db As NotesDatabase ' initially Nothing
m_paddockView As NotesView ' ditto
...
Public Property Get Database( )
If m_db Is Nothing Then
Set m_db = New NotesDatabase(m_server, m_filepath)
If Not m_db.IsOpen Then
Error ERR_HM_NODATABASE, "Error in HorseManager.Database: can't open '" & m_filepath & "' on '" & m_server & "'"
End If
End If
Set Database = m_db
End Property
Public Property Get PaddockView() As NotesView
If m_paddockView Is Nothing Then
Set m_paddockView = Database.GetView(PADDOCK_VIEW_NAME)
If m_paddockView Is Nothing Then
Error ERR_HM_NOPADDOCK, "Error in HorseManager.PaddockView: view '" & PADDOCK_VIEW_NAME & "' not found."
End If
End If
Set PaddockView = m_paddockView
End Property
End Class
The property routine Database does the work of opening the database only once, the first time it's called. Thereafter, it notices the database is already stored in the private property m_db, and just returns the value from there. The PaddockView computed property works the same way, but since it uses the Database property internally, it might end up doing nothing (if the view has been requested before), or it might look up the view, or it might open the database and look up the view, if the database wasn't opened yet. Obviously we can continue the chain, e.g. we might have a GetPaddock(paddockName$) method that uses the PaddockView property to get its lookup view. And the on-demand properties don't have to be Public, either -- I've had occasion to use them internally in the class for values we don't need to expose to the world.
Notice how I throw errors if there's a problem computing the requested value. This is preferable to just returning a value that's not usable, because we explain the problem at the real point of failure. If PaddockView just returned Nothing in case of a nonexistent view, the caller wouldn't see that there was a problem until it tried to use the returned value, getting the much less informative error "Object variable not set." The caller of course has the option of trapping (or ignoring) the error and taking corrective action in either case, or test the return value of PaddockView before using it, but since this code provides a unique error number, it's easier to code something that addresses the specific issue or displays a useful message to the user. E.g. if PaddockView returns Nothing, you might want to know whether the problem is with the database or the view.
The price we pay for not having to calculate the values each time they are needed, is that we have to remember to clear the cached values should they become invalid. I generally define a private method to erase the cached data, which I call from various places in other methods when a relevant value changes (there might be more than one such routine, if there are a bunch of cached values that aren't all necessarily invalidated at the same time).
Set m_db = Nothing
Set m_paddockView = Nothing
End Sub
Public Property Set Filepath As String
m_filepath = Filepath
ClearCache ' any existing database and view objects are no longer valid
End Property
It makes sense to use this technique if the operation that produces the data item is relatively expensive, and if it's possible that the value will not be required by the code that uses the class. In many cases, though, I've found that even aside from performance considerations, it results in cleaner looking code. You don't always have all the information you need to initialize everything in the constructor, and by using this technique, you know you don't have to worry that everything will be assigned in the right order for you to use it -- by using your own property routine internally, it's assigned at the time that you require it, if not before.
Of course, it's a little more expensive and more lines of code to call a property function as opposed to referring to a property member directly, but if they are read-only properties, you need the Property Get function anyway, and the performance cost is small compared to opening a database or view unnecessarily.
Andre Guirard | 19 September 2007 01:39:00 PM ET | Nokomis Beach Coffee, Minneapolis, MN | Comments (6)

