Chain of Functions

Today I found myself needing to trigger two functions in a button’s onRelease event handler. I wanted to keep the ability to install a handler function dynamically, just like you’d do when saying mc.onRelease = doSomething;, but I wanted to add the new function without displacing any previously installed handler. Also I hoped to keep the API simple. See what you think.

In my button’s constructor, I have some code that looks like this:

this.onRelease = FunctionChain.create();
this.onRelease.add(Proxy.create(this, this.broadcastSelectionEvent));

and later, in another part of the forest:

theButton.onRelease.add(buttonReleaseFunc);

And that’s it! When the button is clicked, both functions are triggered.

The FunctionChain class that makes this work is pretty simple, too:

import ascb.util.Proxy;

class com.nodename.utils.FunctionChain
{
static var version:String = "$Id: FunctionChain.as,v 1.1.1.1 2005/07/18 00:36:48 ashaw Exp $";

static function create():Function
{
	return (new FunctionChain())._execute;
}

/**************************
 * End of public interface!
 *************************/

private var _functions:Array;
private var _execute:Function;

private function FunctionChain()
{
	this._functions = [];
	this._execute = Proxy.create(this, this.executeChain);
	this._execute.add = Proxy.create(this, this.add);
}

private function executeChain():Void
{
	var numFuncs:Number = this._functions.length;
	for (var i:Number = 0; i < numFuncs; i++)
	{
		this._functions[i]();
	}
}

// func had better be created by Proxy or Delegate!
private function add(func:Function):Void
{
	this._functions.push(func);
}

}

Simple, yes, but the details may not be clear to everybody, so let's take a look at the code. (If it's all clear to you, just skip to the usage notes at the end.)

The heart of the class is just an Array of functions; the add(func) method just adds another function to the Array, and the executeChain() method executes each of the functions in turn. Nothing special there. It's the stuff in the constructor and in the static create() method that lets us hide things away and simplify the API.

First the Array of functions is initialized to an empty Array. No problem. Then we set _execute, a Function variable, to refer to the executeChain method, but not directly. We use a Proxy, which carries a reference to the guy who's going to execute the method as well as a reference to the method itself. This is important (stop me if you've heard this) because when the function finally gets called, we're going to need to find that guy, and probably no one around is going to be pointing to him. And since it's he himself who'll be executing his own method, the method doesn't have to be public.

The Proxy class is part of Joey Lott's ascb library. Its usage is just like that of mx.utils.Delegate, except that Proxy also allows you to specify arguments that will be passed to the function when it runs. So I've gotten into the habit of using Proxy rather than Delegate.

OK, back to _execute. This, as I said, refers to the executeChain() method, which is just what we want our onRelease method in the button to do. We'll be returning _execute to the button so he can set his onRelease handler to it. But there's another piece we need to take care of, and that's providing a way to add functions to the chain. So we create another Proxy, referring to the FunctionChain's add() method, and we tack it right onto _execute. Remember in ActionScript a Function is an Object, so like any other Object it can take on properties -- either data members or other Functions. (And since Function is a dynamic class, it can take on properties that were not declared in the class definition.) I believe it was pretty common to add properties to Function objects in AS1, but this is the first time I've found an excuse to do it in AS2.

So we're going to return _execute to the button. We could provide an accessor method in FunctionChain, but we note that there's really no need for the button to have a handle on the FunctionChain object at all, so we can just make the constructor private and wrap the whole interface up in one static function, create().

A couple of notes about usage:

First, it's important that the functions you add to the FunctionChain also be created by Proxy or Delegate.

Second, you can use FunctionChain to build any function; there's nothing about it that restricts it to Button or MovieClip event handlers.

  • http://www.teknision.com Tony MacDonell

    Even though this is a neat class, I question the type of architectural patterns that it’s usage will lead to.

    It is neat to be able to simulate/reformat a function at runtime, but here’s the issues I see arising:

    1/ Readability of code.

    If this was used extensively in a project, following the bootstrapping of the code becomes extremely difficult. In large OO projects it can sometimes be difficult anyways, but this adds yet another level of abstraction at the class level.

    To me it seems as though you are trying to deprecate the idea of a class level method with this, and that is something that architecturally is very dangerous as it is a fundamental aspect of OOP.

    The wrapping class owns the task that the child item’s onRelease method is triggering, and a Delegate or Proxy should be there for the sole purpose of sending notification of that event.

    Using your class you are breaking down that ownership to the point that you invalidate the wrapping class’s presence in the architecture. It does not own the process anymore, an abstract object does instead.

    2/ Inheritance, Sublassing and overwriting tossed out the window.

    If one was to again use this extensively in a project, Subclassing would become a nightmare. The fact that you are trying to make dynamic methods means that classes that utilize this would be nearly impossible to subclass.

    Just some thoughts and criticisms, but I do see it’s cool factor though.

  • http://nodename.com Alan Shaw

    Tony, I’m not sure what you’re referring to as the wrapping class or the child item, so I may be mistaking your meaning, but I believe you’re criticizing only one thing: the ability to dynamically alter a member function of an object. As you know, I’m not providing that; it’s there in ActionScript already. But yes, it does raise some concerns, as you yourself wrote about just today in your post Delegates, be careful…. I’ll summarize if I may: First, you show that Delegate.create(someObject, someFunction) is inherently dangerous for the very reason that "if for some reason someFunction is overwritten at runtime (which is common in AS) then your delegate no longer refers to the correct function." So the Delegate would then not work correctly, and furthermore it would waste memory because the old version of someFunction would have been garbage-collected if your Delegate wasn’t still holding a reference to it. You propose a version of Delegate that uses a String name of the function rather than a Function reference. This will always resolve to the current version of the member function. Sounds good to me. (A little slower, though, I expect.)

    (Then you speak about potential problems Delegate can cause in the EventDispatcher context — also quite interesting, but I don’t think dynamic functions is relevant to that.)

    But I thought your complaint in your blog was not with dynamic assignment of functions — that’s a given in ActionScript — but with the implementation of Delegate in such an environment. Or as my uncle used to say about crossing the street in New York City, "You got to be careful."

    Is there some way my FunctionChain makes the problem worse?

blog comments powered by Disqus