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

PDF:

Object-Oriented Thinking

Just-in-Time References

Another Delegate Example

Issue: 6.5 (July/August 2008)
Author: Charles Yeomans
Article Description: No description available.
Article Length (in bytes): 5,039
Starting Page Number: 46
RBD Number: 6517
Resource File(s): None
Related Link(s): None
Known Limitations: None

Excerpt of article text...

Let me begin this issue's column with a brief sketch of a logging framework I wrote. The idea is that one has various targets for log messages -- file, console, system, memory. The targets are to be assigned and possibly changed at runtime. It supports sending messages to multiple targets, and filtering of messages by urgency -- fatal, error, warning, status, debug, trace. This is expressed in code as a class interface, LogTarget, and several classes implementing LogTarget -- LogTargetFile, LogTargetConsole, etc. A LogTargetComposite class contains references to other LogTargets, and handles the forwarding of messages to these targets. And a LogTargetFilter class, which wraps another LogTarget, filters the messages it passes on. Finally, there is a Logging module that holds a reference to a default target, plus methods that provide convenient, global access to that target. This framework provides considerable flexibility. For example, suppose I decide that all messages should be written to the system log, and error messages should be written to the console. I can easily build a LogTarget that expresses this scheme. Note the absence of any branching logic. dim consoleFilter as new LogTargetFilter consoleFilter.LogError = true consoleFilter.Target = new LogTargetConsole Logging.DefaultTarget = new LogTargetComposite(consoleFilter, new LogTargetSystem) As it turns out, though, I did not anticipate everything. In my framework, the LogTargetFile class has a constructor that takes a FolderItem representing the file to which messages are written. In applications, I typically add to the App class a method that provides the log file. Private Function LogFileName() as String Then I create the log target object as follows. Logging.DefaultTarget = new LogTargetFile(App.LogFile) But later I realized that I want to create a new log file each day, with the name date-stamped. Adding the date-stamping requires no more than a simple change to the LogFileName function; I pass it a Date object, and let it create the name. With this change, my code now looks like this. Logging.DefaultTarget = new LogTargetFile(App.LogFile) This change has created a new problem for me, though. How do I change the FolderItem each day? There are several ways to do this poorly, hackishly, or unreliably, and I considered most of them. As is so often the case, brute force proved to be the simplest expedient. Sub DoEvents(milliseconds as Integer) Logging.DefaultTarget = new LogTargetFile(App.LogFile) super.DoEvents milliseconds End Sub I could have used a Timer to achieve the same result, but this was for a console application, in which Timer action event handlers are called by DoEvents, so why not save the overhead of the extra function call? Some nights later, while trying to convince my brain to quit trying to execute REALbasic code so that I could go to sleep, a blinding flash of the obvious struck. Instead of storing a reference to the FolderItem, store a reference to something that could provide a FolderItem; that is, use a delegate. So I defined a new delegate type. Delegate Function LogFileSourceDelegate() As FolderItem Then I added a new constructor to LogTargetFile. Sub Constructor(logFileSource as LogFileSourceDelegate) LogTargetFile stores a reference to the delegate, and invokes it when it needs to record a log message. Now my code to set up logging looks like this. Logging.DefaultTarget = new LogTargetFile(AddressOf App.LogFile) This bit of indirection is worth pondering. Here is the implementation of App.LogFile. Private Shared Function LogFile() As FolderItem dim f as FolderItem = Directories.logs if f is nil then return nil end if dim d as new Date f = f.Child(d.SQLDate + ".log") if f is nil then //log problem directly to system end if return f End Function LogFile computes the FolderItem on request. And LogTargetFile asks for the FolderItem (by invoking the delegate) as needed. In other words, our scheme uses lazy evaluation. And, perhaps more importantly, we have reduced the program state shared between the App object and the LogTargetFile to the LogFile delegate, which might be more properly characterized as shared plumbing. You might point out that this use of delegates and just-in-time evaluation appears to come at a cost in execution speed. If that turns out to be a problem, or you just worry about things like that, you can easily do some caching inside App.LogFile, or inside LogTargetFile, without touching the other. But 99% of the time, the just-in-time approach will do. And I claim that this approach will be more reliable 100% of the time. Finally, you may have noticed that LogFile is a shared method. As a consequence, creating a delegate to it does not create another reference to the App object. It is very easy to create circular references with delegates, and there is no way to spot them in the debugger. Any time you store a reference to a delegate in another object, you should bear this in mind.

...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