Special

Clearance Sale!

We've been publishing for over five years now and it's time to clear out our inventory of back issues, so we're slashing prices!

RBD Magazines

Check out this amazing clearance sale of all our past issues. Missing some issues? This is a great time to complete your RBD collection. Save up to 40% off the regular price of our printed back issue packages. These prices are only good until the end of the year May 2008 and supplies are limited, so place your order today.

Article Preview


Buy Now

Print:
PDF:

Object-Oriented Thinking

A Delegate Sampler

Methods as Objects

Issue: 6.2 (January/February 2008)
Author: Byby Charles Yeomans
Article Description: No description available.
Article Length (in bytes): 6,499
Starting Page Number: 38
RBD Number: 6216
Resource File(s): None
Related Web Link(s):

http://www.declareSub.com/

Known Limitations: None

Excerpt of article text...

REALbasic 2007r4 added delegates to the language. In short, a delegate is a reference to a global, shared, or object method. They are a welcome addition to the language, because they are a solution to the problem "how do I specify a method at runtime?". To whet your appetite for delegates, I offer here a few amuses-bouchebecause foreign phrases should be italicized, no?. Simplify XML Processing Code that converts an XML document into something else frequently has at its heart a big select case statement. Here is some code lifted from a method that imports an XML dump of a database. Sub ProcessXML(xml as XMLDocument) ... dim fieldType as String = fieldNode.GetAttribute("fieldType") select case nodeName case "Boolean" ProcessBooleanNode fieldNode case "Integer" ProcessIntegerNode fieldNode case "VarChar" ProcessVarCharNode fieldNode case "BLOB" ProcessBLOBNode fieldNode case "Date" ProcessDateNode fieldNode else //oops end select End Function added codecomment You can speed this code up significantly by using delegates. Let's assume that the method above and what follows all takes place in a single module. Define a delegate Sub XMLNodeProcessor(node as XMLNode), and a property NodeProcessors as Dictionary. Now add the following method to initialize NodeProcessors. Function NewNodeProcessors() as Dictionary dim d as new Dictionary d.Value("Boolean") = AddressOf ProcessBooleanNode d.Value("Integer") = AddressOf ProcessIntegerNode d.Value("VarChar") = AddressOf ProcessVarCharNode d.Value("BLOB") = AddressOf ProcessBLOBNode d.Value("Date") = AddressOf ProcessDateNode return d End Function Sub ProcessXML(xml as XMLDocument) ... dim processor as XMLNodeProcessor = NodeProcessors.Lookup(fieldNode.GetAttribute("fieldType"), nil) if processor <> nil then processor.Invoke fieldNode else //handle error end if End Function added codecomments The resulting code is probably faster, and certainly cleaner and easier to maintain. Replace Events With Delegates Let's suppose that you have Class1 object that needs to perform some action periodically. You use a Timer to perform periodic actions, so you could write a Timer subclass with a Class1 property, and implement its Action event handler method to call some method of the Class1 object. After writing a few such subclasses, you may realize that it's the same code; all that changes is the method being called in the Action event handler. Delegates allow you to set that method at runtime, so that you only have to write one Timer subclass. Define a delegate Sub TimerActionHandler(t as DelegatingTimer) and a subclass DelegatingTimer of Timer. Add a public property ActionHandler as TimerActionHandler to DelegatingTimer. Then implement the Action event handler as follows. Sub Action() if me.ActionHandler <> nil then me.ActionHandler.Invoke me end if End Sub It is perhaps useful to compare this solution to one using a class interface, which is what you would do prior to REALbasic 2007r4. Define a class interface TimerActionHandler with a method HandleAction. DelegatingTimer would then have a property ActionHandler as TimerActionHandler and its Action event handler would be implemented as follows. Sub Action() if me.ActionHandler <> nil then me.ActionHandler.HandleAction me end if End Sub Here, the use of a delegate offers some advantages over a class interface. First, a delegate can be created from a global, shared, or object method. So a module could supply behavior to a DelegatingTimer. But modules cannot implement class interfaces. Second, an object can use two or more DelegatingTimers, passing each a different method delegate. This is possible with the class interface approach, but only through ugly, bug-prone code. Third, suppose you want to use a DelegatingTimer with a REALbasic framework class. You would either need to write a subclass that implements TimerActionHandler, or write an adapter class that wraps the framework class in something that implements TimerActionHandler. Reuse Loop Code Given an array of type String, you need to remove all blank elements. The code to do it is takes a little work to get right. The standard mistake is to write Sub RemoveBlonks(theList() as String) for index as Integer = 0 to UBound(theList) if theList(index) = "" then theList.Remove index end if next End Sub which, of course, fails to take into account that the array is being modified during the loop. There are a couple of ways to handle the in-place modification. dim index as Integer = 0 while index <= UBound(theList) if theList(index) = "" then theList.Remove index else index = index + 1 end if wend for index as Integer = UBound(theList) downto 0 if theList(index) = "" then theList.Remove index end if next But let me propose an alternative. Instead of in-place modification, generate a new array. This allows the use of the most intuitive loop. Function FilterBlonks(theList() as String) as String dim outputList() as String for index as Integer = 0 to UBound(theList) if theList(index) <> "" then outputList.Append theList(index) end if next return outputList End Sub As it turns out, this function is also more than one-hundred times faster than RemoveBlanks. But the best part is that because the loop code is independent of the operation, we can factor the Boolean test code into a delegate. Let's introduce a new delegate type Function StringFilterDelegate(s as String) as Boolean. The convention will be that a function implementing it should return true if the argument s passes the filter and should be added to the output array. Then we can refactor the code above. Function NotBlank(s as String) as Boolean return (s <> "") End Function Function Filter(theList() as String, filter as StringFilterDelegate) As String() dim outputList() as String for i as Integer = 0 to UBound(theList) if filter.Invoke(theList(i)) then outputList.Append theList(i) end if next return outputList End Function In other code, these methods are called as follows. dim blankFreeList() as String = Filter(theList, AddressOf NotBlank) Now this code is somewhat slower because we are invoking a method instead of writing a test in-line. But in return, we have code that is easily testable and reusable.

...End of Excerpt. Please purchase the magazine to read the full article.

Article copyrighted by REALbasic Developer magazine. All rights reserved.


 


|

 


Weblog Commenting and Trackback by HaloScan.com