AOP vs. The Interceptor vs. The Template

No, it's not some crazy tag-team wrestling line-up. I was side-tracked in one of my Googling expeditions and came across a blog entry by Ted Neward titled Setting the Story Straight: AOP != Interception. In it, Ted provides an interesting comparison of the Interception pattern and Aspect-Oriented Programming.

Interception and AOP also have parallels with the Decorator pattern, filters and the Template method (in which you set up your Template methods so that they call hook operations that can be overriden by subclasses.) These concepts and patterns can be useful in Flash and ActionScript development too. For example, the Template pattern is used for the base classes in ARP and I had used Interception in the past to decorate the functionality of a method (I'll be damned if I can remember what exactly it was for.)

After reading Ted's post, I thought I'd whip up a reusable way to easily decorate methods and came up with the Aspect class presented below (it should perhaps rather be called Interceptor or MethodDecorator.) The Aspect class allows you to modify the behavior of a method by specifying that another method (which I've called an Advice, following the term used in AOP) be executed before (default) or after the method.

This is a naive first implementation and I have neither given it too much thought nor tested it out exhaustively so treat it as experimental code and feel free to send me suggestions and improvements. Here it is for you to play with...

// Class: Aspect
// Allows you to weave in advice (other methods) before and after the original method.
// Copyright (c) 2005 Aral Balkan
// Released under the MIT License.
class Aspect
{
    var originalObject:Object;
    var originalMethodRef:Function; // Reference to the original method
    var originalMethodName:String;
    var copyOfOriginalMethod:Function; // Local copy of original function
    var advice:Function; // Code to weave in
    var after:Boolean = false;

    function Aspect (obj:Object, methodName:String, advice:Function, after:Boolean)
    {
        originalObject = obj;
        originalMethodName = methodName;
        originalMethodRef = obj[methodName];

        // Save a local copy of the original method
        copyOfOriginalMethod = obj[methodName];

        // Save the advice method
        this.advice = advice;

        // Should the advice be executed after the original
        // method? (Defaults to false so the advice is executed
        // prior to the original method.
        if (after != undefined) this.after = after;

        // Overwrite the original method with a proxy function
        // to relay calls to the Aspect object
        obj[methodName] = function()
        {
            // Call the execute method on the Aspect
            arguments.callee.aspectRef.execute.apply (arguments.callee.aspectRef, arguments);
        }

        // Add a reference to this instance on the
        // target method's activation object so it has a
        // way to refer to us.
        obj[methodName].aspectRef = this;
    }

    // Executes the advice and the original method.
    function execute()
    {
        // Should the advice be applied prior to the original method call?
        if (!after) advice.apply (originalObject, arguments);

        // Original method call
        copyOfOriginalMethod.apply (originalObject, arguments);

        // Should the advice be applied after the original method call?
        if ( after ) advice.apply(originalObject, arguments);
    }
}

To test it out, enter the following script into the first frame of an empty FLA placed in the same folder as the Aspect class:

// Create the original method
originalMethod = function() { trace ("Original method") };

// Create two new advices
firstAdvice = function() { trace ("Before original method") };
secondAdvice = function() { trace ("After original method") };

// Create the two aspects
firstAspect = new Aspect(this, "originalMethod", firstAdvice );
secondAspect = new Aspect(this, "originalMethod", secondAdvice, true);

// Call the original method
originalMethod();

The output should be:

Before original method
Original method
After original method

I'm sure you'll find some cool uses for this! :)

Comments