Article Preview
Buy Now
| Print: | |
| PDF: |
Object-Oriented Thinking
Module Properties
Not As Trivial As They Look
Issue: 6.3 (March/April 2008)
Author: Charles Yeomans
Article Description: No description available.
Article Length (in bytes): 5,151
Starting Page Number: 40
RBD Number: 6318
Resource File(s): None
Related Link(s): None
Known Limitations: None
Excerpt of article text...
Adding a module property to a project seems a trivial thing. But it can introduce a surprising amount of complexity into your code. Here we take a look at how to isolate that complexity. Let's begin with some typical code. First is the module and the property LookupMap as Dictionary. Next is an initialization method. Sub InitializeLookupMap() static isInitialized as Boolean = false if not isInitialized then LookupMap = new Dictionary LookupMap.Value("key1") = "value1" LookupMap.Value("key2") = "value2" LookupMap.Value("key3") = "value3" isInitialized = true end if End Sub Now, the first problem: when is the property to be initialized? All other code that uses LookupMap needs to know this information. This is an insidious form of coupling. The method is implemented so as to initialize only once, so we could simply InitializeLookupMap every time that we need to be certain of initialization. This has a performance cost, and is a subtle version of code duplication. Second, any other code can change the value of LookupMap. Unless this is what you want - and it usually is not - you can spend a long time looking for unintentional changes. And there is also the possibility that other code can change entries in LookupMap. If this is not what you want, you could be in for some debugging time. Let's refactor the code, tackling my objections in reverse order. We can prevent the Dictionary object itself from being replaced by providing public access only through a computed property. Rename the property to pLookupMap, set its visibility to private, and add a computed property LookupMap as Dictionary. Leave the Set handler unimplemented, and implement its Get handler as follows. Get return pLookupMap End Get Because the Set handler is unimplemented, the compiler will not allow assignment to LookupMap. It is still possible for another object to modify the contents of the Dictionary object returned by LookupMap. Unless you want this, it is better to prevent it. To do so, we return a copy of pLookupMap. Since copying a Dictionary is a generic operation, we write a little private method to do it. Private Function Copy(d as Dictionary) As Dictionary if d is nil then return nil end if dim dCopy as new Dictionary for i as Integer = 0 to d.Count - 1 dCopy.Value(d.Key(i)) = d.Value(d.Key(i)) next return dCopy End Function Now we rewrite Get to use it. Get return Copy(pLookupMap) End Get Next, we tackle initialization. The easiest way to eliminate the need to know when initialization occurs is to make LookupMap self-initializing. Get InitializeLookupMap return Copy(pLookupMap) End Get But this code is not as good as it could be. InitializeLookupMap proceeds by side-effect; that is, it is reaching outside the method to change the state of the application. If it does not do its job correctly, then the application may be left in an invalid or unstable state. What InitializeLookupMap really does is to create a new Dictionary and set it up. So let's change it from a subroutine to a function, and give it a new name that better describes it. Function NewLookupMap() as Dictionary dim d as new Dictionary d.Value("key1") = "value1" d.Value("key2") = "value2" d.Value("key3") = "value3" return d End Function Note that I have removed the isInitialized check; it is now better performed in the Get handler. Get if pLookupMap is nil then pLookupMap = NewLookupMap end if return Copy(pLookupMap) End Get As a result of the changes, the only access to pLookupMap is from within the Get handler. Since the Get handler is a global function, we can replace the private property, which other code in the module could change, with a static variable whose visibility is limited to the function. Get static pLookupMap as Dictionary = NewLookupMap return Copy(pLookupMap) End Get The switch to a static variable also allows us to let the REALbasic runtime do the work of the isInitialized test, as initial assignment to the static variable is performed only the first time the line of code is executed. The resulting code suggests another question: why not just return NewLookupMap every time, instead of saving the reference pLookupMap? Here, the work performed by Copy is comparable to that performed by NewLookupMap, so caching the value is not really improving performance. Code for which the cost of calling LookupMap is significant should cache the returned value. And there is an alternative that we have not considered: replace LookupMap with a function LookupValue(key as Variant) as Variant, and possibly a function LookupKeys() as Variant(). This provides essentially the same functionality as the Dictionary copy, and allows us even more flexibility in the private implementation. So adding a property to a module is not such a trivial thing after all. The trick is to understand the complexity the addition introduces, and to confine as much of that complexity as possible to the property. Managing the complexity there means that it does not have to be done, perhaps badly, by other code.
...End of Excerpt. Please purchase the magazine to read the full article.
Article copyrighted by REALbasic Developer magazine. All rights reserved.
|







