The Sharendipity Blog » Post 'Moving to Flash, Part 4: Anonymous Classes and the Adapter Pattern'

Moving to Flash, Part 4: Anonymous Classes and the Adapter Pattern

This is the fourth in a series of posts describing our port from Java to Flash. For the previous posts, see:

Part 1: We’re Moving to Flash. Here’s Why
Part 2: Important Differences Between Java and ActionScript 3
Part 3: Beginning the Actual Port

This post describes an implementation that allows you to get around the lack of anonymous classes in ActionScript. My biggest complaint about this solution is that it moves compile time errors to runtime. It does, however, hide these issues from the consumers of the implementation.

Adapters

One of the reasons that we were able to perform the port so quickly was because we have a generic asset description. All of our data is passed from the server via XML, so we just needed to figure out how to handle that data in ActionScript. We rely heavily on the adapter pattern to do this. In Java, we used anonymous classes to implement these adapters.

Anonymous classes are nice because using them allows you to define functionality through a particular interface. Here’s an example of an implementation of our XmlStringAdapter:

addAdapter(ATTRIBUTE_NAME, new XmlStringAdapter() {
	public void setString(String sValue) {
		// handle setting
	}
	public String getString() {
		return [object's local value];
	}
}

Ignoring the actual implementation of the getter and setter, it’s easy to see that we have provided an adapter that allows us to set and get values through a specific interface. Pretty handy, but impossible in ActionScript, at least in this manner.

In order to do this in ActionScript, we rely on flash.utils.Proxy. This is actually based off of the Decorator pattern in the book Object-Oriented ActionScript 3.0. We have our own class called DynamicImplementation:

package com.sharendipity.util {

	import com.fived.error.RuntimeException;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;

	public dynamic class DynamicImplementation extends Proxy {

		private var mtProperties:Object = new Object();

		public function DynamicImplementation() { }

		/**
		 * Add a function to the dynamic implementation.
		 */
		protected function addFunction(sName:String, tFunction:Function):void {
		    if (mtProperties[sName] == null) {
		        mtProperties[sName] = tFunction;
		    }
			else {
			    throw new RuntimeException("Duplicate function: " + sName + " in object: " + this);
			}
		}

		/**
		 * Execute the dynamically added functions.
		 */
		override flash_proxy function callProperty(name:*, ...rest):* {
			var tReturn:Object = null;
			var tFunction:Function = mtProperties[name];

			if (tFunction != null) {
			    try {
			        tReturn = tFunction.apply(mtProperties, rest);
			    }
			    catch (tError:ArgumentError) {
			        throw new RuntimeException("Incorrect number of parameters to function: " + name);
			    }
			}
			else {
				throw new RuntimeException("Function [" + name + "] is not initialized for object: " + this);
			}

			return tReturn;
		}
	}
}

That’s a lot to digest. In essence, this class allows you to add dynamic, named functions to an object in a controlled manner. Note that there are no publicly visible functions. The idea is that your own objects can extend DynamicImplementation in order to provide their own interface. Let’s look at an example of how the above XmlStringAdapter might be implemented:

public dynamic class XmlStringAdapter extends DynamicImplementation implements IXmlStringAdapter {	

	private static const GETTER:String = "getter";
	private static const SETTER:String = "setter";

	private static const GET_VALUE_TYPE:String = "get_value_type";
	private static const COMPARE_TO:String = "compare_to";

	public function XmlStringAdapter() { }

        /**
         * Add the setter implementation to this adapter.
         */
	public function addSetString(tFunction:Function):void {
		addFunction(SETTER, tFunction);
	}

        /**
         * Add the getter implementation to this adapter.
         */
	public function addGetString(tFunction:Function):void {
		addFunction(GETTER, tFunction);
	}

	public function getString():String {
		return this.getter();
	}

	public function setString(sValue:String):void {
		this.setter(sValue);
	}
}

This implementation shows that we have four publicly available functions. It is expected that the IXmlStringAdapter exposes only the getString() and setString() functions and this is the interface that will be passed around, so most people will not see the addSetString() and addGetString() functions. Those functions are designed to be available only to the creator in order to define the dynamic functionality.

The new XmlStringAdapter class achieves a lot of what we’re looking for from anonymous classes. Mainly, external consumers of the class access it through a specific interface which is unchanged from the previous implementation. Furthermore, this interface is compile-time checked. As we’ll see later, however, the internals of the adapter are not.

If you look a little closer at the implementation, you’ll also notice that the underlying function names that the adapter is using are “getter” and “setter.” It is important that these names are different than the externally visible names of the functions in the class. If not, they can never get called. The names are also arbitrary, they are simply internal names that the object uses to call the functions being defined.

Let’s look at the corresponding ActionScript example that will implement the same functionality that we showed in Java in the beginning of this post:

var tAdapter:XmlStringAdapter = new XmlStringAdapter();
tAdapter.addSetString(
	function setString(sValue:String):void {
		// perform setter
	}
);
tAdapter.addGetString(
	function getString():String {
		return [object's local value];
	}
);
addAdapter(ATTRIBUTE_NAME, tAdapter);

As you can see, this is very similar to the original Java implementation. The main difference is that functionality is added through the addGetString() and addSetString() functions, rather than defining the implementation of the class itself.

The important underlying difference, however, is that several problems are not checked at compile time. This can create some frustrating results. First, it’s possible that you switched the getString() and setString() functions and passed the setter into addGetString(), and the getter into addSetString(). Second, you need to look into the XmlStringAdapter class to determine what the correct parameters are to the function that you’re implementing. If the types or number of parameters differ, you’ll get a runtime error.

Runnables

I mentioned in the previous post that the Runnable object can be implemented differently as well. In fact, it can use the same underlying DynamicImplementation:

package com.sharendipity.util {

	import com.fived.error.ExceptionHandler;
	import flash.utils.flash_proxy;

	public dynamic class Runnable extends DynamicImplementation implements IRunnable {

		public static const RUN:String = "perform_run";

		public function Runnable() { }

		public function addRunnable(tFunction:Function):void {
			addFunction(RUN, tFunction);
		}

		public function run() {
			this.perform_run();
		}
	}
}

Note that we will also have the Runnable class implement an IRunnable interface so as to hide the addRunnable() function from the consumers.

While ActionScript is inherently single-threaded, Runnables can be used to define tasks that run at specified intervals as managed by a global task manager. This can be nice for a lot of different reasons. As one example, which we’ll see in the next post, using Runnables may better facilitate the move from a multi-threaded environment to a single-threaded one when performing communication with the server.

Conclusion

You can get pretty close to the implementation of anonymous classes in ActionScript, but it’s certainly not fool proof. You’ll have to do more debugging at run time so be sure to check over your code with a fine-toothed comb when you come across these issues.

The next post will address issues of porting your communication code in a multi-threaded environment to a single-threaded one.

Reblog this post [with Zemanta]
Bookmark and Share
Tags:, , , , , ,

6 Responses to “Moving to Flash, Part 4: Anonymous Classes and the Adapter Pattern”

  1. I don’t know how much this helps, but implementing a dynamic Runnable interface runs as follows:
    ——————————————–
    interface Runnable{
    dynamic public function run():Void;
    }
    class ConcreteRunnable implements Runnable{
    dynamic public function run():Void{
    trace(”run”);
    }
    public function new(){
    //constructor
    }
    }
    class TestDynamic{
    public static var instance (default, null) : TestDynamic;

    function new(){
    var a = new ConcreteRunnable();
    a.run();
    a.run = function(){
    trace(”dynamic run”);
    }
    a.run();
    }

    public static function main(){
    if ( instance == null )
    instance = new TestDynamic();
    }
    }
    ————————————–
    //in file build.hxml:

    -swf TestDynamic.swf
    -main TestDynamic
    ————————————–
    //then call from the command line:
    haxe build.hxml
    ————————————–
    output:
    ConcreteRunnable.hx:4: run
    TestDynamic.hx9:9: dynamic run

    Whether or not this gives you any compile time advantage I don’t know, but it might cut down on the cruft. That’ll work as is.

  2. @Ob1Kn00b I’ll be honest, I was wondering when someone would tell me that I was missing something. Learning ActionScript in a couple of weeks will leave you bound to look past a particular implementation. Your implementation works well, but I have to say that part of me likes that using my addRunnable() implementation lends some comfort because it operates through a defined interface. As a Java programmer, it is a bit strange that you can override a function by setting the property to a new one. Just personal preference though. Thanks for the comment, I’ll look into using it.

  3. @Ob1Kn00b
    .
    You are typing AS2 code. In AS3 you can not say ‘dynamic public’ in an interface. On top of that, in AS3 methods are sealed into their classes as traits and can not be assigned a new body.
    .
    .
    Greetz Erik

  4. I seem to have found myself advocating Haxe here. If you’re new to Actionscript, I’d better cut it, to avoid confusion.

  5. @Erik thanks for clearing this up. I was just about to go play around with it.

  6. Your implementation works well, but I have to say that part of me likes that using my addRunnable() implementation lends some comfort because it operates through a defined interface.www.communicationalltime.com

Leave a comment

© 2009 The Sharendipity Blog is powered by WordPress