ActionScript - Reflective Programming
This is the follow up article for my speech in the 360|Flex 2008 conference.
If you would like to get a field of an object, but you do not know the field name until runtime, you are likely to use reflective programming to resolve this matter.
Reflective programming is the process of a program that can observe its own structure and execute its own functions during runtime.
In ActionScript, there is a set of functions under the flash.utils package that handle reflective programming. Thanks to these functions, you can accomplish the following operations:
- determine the type of a class
- dynamically retrieve the details of the superclasses of a class and the variables, constants, variables, constructor, and methods defined in the class
- create an instance of a class whose name is not known until at runtime.
- execute methods of an unknown type class during runtime
Benefit of Using Reflective Programming
Before we jump into the technical aspects of how to program reflective programming in ActionScript, we can discuss some of the benefits of applying reflective programming in coding applications.
In a good programming design, common functions are written to operate on the same types and extended types of objects. This practice is called generic programming. Generally, we like to apply this practice to our program because it reduces the amount of duplicated source code. However, these extended types of objects may have different method names and return types that need to be invoked and retrieved. Inevitably, we need to hard-coded the methods name in these common functions. Consequently, these functions are not generic enough to eliminate duplicated code. In some worse case scenarios, more replicated functions and variables are generated. This totally impacts the design.
To resolve this issue, we need to have a mechanism to dynamically pick up the methods from the object and execute them at runtime. We can adopt reflective programming because it has the ability to observe and modify program execution dynamically. It can definitely decrease the instances of hard-coding variables and methods in the code. As a result, it generalizes the program.
Reflective programming also can extend an existing programming deign to a better one, because it makes the program possible to utilize generic type of code execution. Hence, it allows a program to adapt to different situations dynamically during runtime.
Implementation
Now, it is ready for us to construct some real tasks on reflective programming. As a matter of facts, reflective programming involves two entities: observes an object's own structure and executes the object's own functions. The flash.util.DescriptionType package returns the details of an unknown type object during the flow of execution.
Here is an example. We have got an unknown type of object, demo, and we will like to extract the details from this object. We can then apply the descriptionType() function to the object.
...
var descriptionXml: XML = descriptionType( demo );
It returns the details of the object in a form of XML document.
<extendsClass type="Object"/>
<method name="addValue1" declaredBy="SampleClass" returnType="int">
<parameter index="1" type="int" optional="false"/>
</method>
<method name="displayValues" declaredBy="SampleClass" returnType="void"/>
<variable name="value2" type="int"/>
<constant name="FIRST_CONSTANT" type="String"/>
<constant name="SECOND_CONSTANT" type="String"/>
<method name="addition" declaredBy="SampleClass" returnType="int"/>
<variable name="value1" type="int"/>
<method name="subtraction" declaredBy="SampleClass" returnType="int"/>
</type>
From the XML document, we can uncover the class type of the demo object. We can also reveal the constants, variable names and method names that are defined within it. This is the process of how we can use the reflective programming in ActionScript to observe an object at runtime.
If you would like to know the details of the tags defined for the XML, you can visit the Adobe Flex Live Doc to learn more. http://livedocs.adobe.com/flex/2/langref/flash/utils/package.html
In ActionScript, we can use the XML operations to extract the content from an XML document. In our case, we retrieve the details in the following fashion:
descriptionXml..variable[0].@name; // returns the name of the first variable defined in the object
descriptionXml..variable[0].@type; // returns the type of the first variable defined in the object
descriptionXml..method[0].@name; // returns the name of the first method defined in the object
descriptionXml..method[0].@returnType; // returns the type of the first method defined in the object
descriptionXml..constant[0].@name; // returns the name of the first constant defined in the object
descriptionXml..constant[0].@type; // returns the type of the first constant defined in the object
In order to execute the methods and the fields of an object, we need to get an instance of it. However, we may not be able to get the instance of the object if the type of the object is not resolved until during runtime. In this case, we need to generate the instance on the fly. The getDefinitionByName() method creates a handle of a class by the given class name.
In the following example, we assume that we do not know the type of the demo class. We use the descriptType() method to resolve the type name of the demo object. Then, we can use the getDefinitionByName() method to create a handle of the class.
...
...
var descriptionXml:XML = describeType( demo );
var className: String = descriptionXml.@name;
var SampleClassRef: Class = getDefinitionByName( className ) as Class;
We can further invoke the methods with the handle of the class just like we normally do with a normal object instance.
SampleClassRef.displayValues(); // executes the displayValues() method
sampleClassRef.addition(); // executes the addition() method
sampleClassRef.substraction(); // executes the substraction() method
sampleClassRef.addValue( 9 ); // executes the addValue() method
However, if we do not even know the names of the methods and variables in the object, we need to resolve these pieces of information dynamically before we are able to invoke them. We can use a flash_proxy to help completing the task.
Flash_proxy is a namespace that permits an ActionScript class to override the default behavior of its operations in an object. These operations are attached to retrieve and modify the properties of the class, as well as execute the methods defined within it. Conclusively, we can adapt this namespace to access and execute unknown type object during runtime.
We can create a helper class which extends the flash.utils.Proxy package to resolve the context of the object dynamically. In the helper class, we employ the flash_proxy namespace methods, getProperty(), setProperty() and callProperty().
In the following piece of source code, we can assume that the obj is an unknown type object. The flash_proxy maps the given names to the corresponding variables and methods.
...
// getProperty(): retrieves a field of the given "obj" object
flash.utils.Proxy( obj ).flash_proxy::getProperty( propertyName.toString() );
// setProperty(): updates a variable in the given "obj" object flash.utils.Proxy( obj ).flash_proxy::setProperty( propertyName.toString(), value );
// callProperty(): executes a method defined in the given "obj" object flash.utils.Proxy( obj ).flash_proxy::callProperty( methodName.toString(), args );
}
We can then incorporate the observation capability of reflective programming that we have discussed above to help completing the task.
var className: String = descriptionXml.@name;
var demoRef: Class = getDefinitionByName( className ) as Class;
var instance: Object = new demoRef();
var myProxy: MyProxy = new MyProxy( instance );
var methodList: XMLList = descriptionXml.child( "method" );
// invoke the second method defined in the unknown object demo myProxy.invokeFunction( methodList[1].@name );
Conclusion In summary, ActionScript uses the flash.utils package to handle reflective programming. The descriptionType() method brings you the details of an object. The getDefinitionByName() method generate an instance of an object by a given object name. Flash_proxy is a namespace that provides functions to access and execute properties from an object during runtime. For the sake of simplicity and space, I can only cover the functions of the package in a certain degree. To learn more, you may visit the Adobe Live Doc for more details.
You can download the Power Point Presentation and the source code.
