Thursday, March 29, 2012

The Intended Use of AddHandler


Real Studio recently introduced the AddHandler feature that lets you wire events up to objects at runtime. The goal is to allow a cleaner separation of responsibilities and hide more implementation details from the outside world. "AddHandler" provides users a means to set up, purely in code, a class that has some events (say a timer) and respond to the events that class defines. It's intended to be in places where you want to hide the implementation since you can't put classes inside other classes. For instance if you want to provide this class to other Real Studio users and don't want to require adding several classes to use your code.

A class that needs a timer to provide a timeout mechanism may do so entirely in code instead of having to declare a special subclass that handles the Timers events.

Situations like this make sense to use AddHandler.

But it's NOT intended to be a substitute for a well designed API, interfaces, or subclasses.

For example a socket class that needs to time out after a certain number of seconds. In prior versions of Real Studio, you would have had to create a new Timer subclass in your project that only exists to handle the Timer's Action event and relay it back to the socket.

With the addition of AddHandler, your socket can simply have a Timer property and hook the event up via AddHandler as follows :

Class FancySocket
Inherits TCPSocket
Private Sub Connected() Handles Event
StartTimeout( 1000 )
End Sub
Private Sub Error() Handles Event
CancelTimeout()
End Sub
Private Sub StartTimeout( period as integer )
mTimer = new Timer()
mTimer.period = period
AddHandler mTimer, AddressOf DoTimeout
End Sub
Private Sub CancelTimeout()
RemoveHandler mTimer, AddressOf DoTimeout
mTimer = nil
End Sub
Private Sub DoTimeout( sender as Timer )
Disconnect()
End Sub
Private mTimer as Timer
End Class

AddHandler, in general, leads to very tightly coupled behavior by design and this isn't what you want in most cases. In fact, usually you want to make your program as decoupled as reasonably possible.

You also need to be careful to manage the reference counting correctly with AddHandler. It's easy to create reference cycles because the behavior is slightly more subtle: using AddressOf on an instance method creates a new Delegate that keeps a strong reference to the base object. If needed, you can use WeakAddressOf to create a Delegate that holds a weak reference to the base object.

In the example above, we have a cycle created in StartTimeout because our socket holds the Timer and the Timer holds the delegate we created via AddressOf. It's not a problem in this case because we always will get the Error event once we've successfully connected.
However if we neglected to remove the handler then its possible we could just leak this object if there were no other references to it in our application.

AddHandler can be very useful but it comes with a cost to you the developer as you have to be more careful about NOT create reference cycles.

Sometimes the right thing to do is the 'old' style with special subclasses or interfaces.

No comments: