Friday, November 16, 2007

Overridding ActionScript Parent Get/Set Methods and Accessor Functions

In my last blog entry, I discussed ActionScript's support for "traditional" get and set accessors like those we are used to in Java. I contrasted these with ActionScript's get and set accessor functions that may provide an indication of what Java properties might be like in Java 7. In this post, I show how the get and set Java-like accessor methods and the get and set accessor functions work in an inherited class.

The first code listing shows the parent class (AccessorDemonstrator.as):

package
{
/**
* This class provides examples of using properties, methods, and accessors.
* It is intentionally placed in an anonymous package to make compilation
* trivial (file can be co-located with consuming MXML file).
*/
public class AccessorDemonstrator
{
private var privateVariable:String = "Private Variable";
private var privateVariable2:String = "Another Private Variable";
public var publicVariable:String = "Public Variable";
protected var someProtectedVariable:String = "Protected Variable";
private var ownPrivateVar:String = "My Own Private Data Member";

public function getOwnPrivateVariable():String
{
return ownPrivateVar;
}

public function get ownPrivateVariable():String
{
return ownPrivateVar;
}

/**
* Provide my privateVariable.
*
* @param My private variable.
*/
public function getPrivateVariable():String
{
return privateVariable;
}

/**
* Set my private variable.
* @param aNewPrivateVariable Value to set my private variable to.
*/
public function setPrivateVariable(aNewPrivateVariable:String):void
{
privateVariable = aNewPrivateVariable;
}

/**
* Access my privateVariable.
*
* @return My private variable.
*/
public function get privateVariableA():String
{
return privateVariable;
}

/**
* @private
*/
public function set privateVariableA(aNewPrivateVariable:String):void
{
privateVariable = aNewPrivateVariable;
}

/**
* Provide my privateVariable2.
*
* @return My private variable 2.
*/
public function get privateVariableB():String
{
return privateVariable2;
}

/**
* Provide my protectedVariable.
*
* @return My protected variable.
*/
public function getProtectedVariable():String
{
return someProtectedVariable;
}

/**
* Provide my protectedVariable.
*
* @return My protected variable.
*/
public function get protectedVariable():String
{
return someProtectedVariable;
}
}
}


The second code listing shows the child class (ChildAccessorDemonstrator):

package
{
/**
* This class provides examples of how to override properties from a parent
* class.
*/
public class ChildAccessorDemonstrator extends AccessorDemonstrator
{
/* The private variables in the parent do not conflict with these because
only the parent can see its data members with the same name. */
private var privateVariable:String = "Private Variable Child";
private var privateVariable2:String = "Another Private Variable Child";
/*
// The varibles below cannot be declared in this class that extends
// AccessorDemonstrator because they are declared in that parent class and are
// thus available to this class. Attempting to redefine them here results in
// the error message:
//
// Error: A conflict exists with inherited definition
// AccessorDemonstrator.publicVariable in namespace public.
//
// Error: A conflict exists with inherited definition
// AccessorDemonstrator.protectedVariable in namespace protected.
//
public var publicVariable:String = "Public Variable Child";
protected var protectedVariable:String = "Protected Variable Child";
*/

/**
* Provide my privateVariable.
*
* @param My private variable.
*/
public override function getPrivateVariable():String
{
return privateVariable;
}

/**
* Set my private variable.
* @param aNewPrivateVariable Value to set my private variable to.
*/
public override function setPrivateVariable(aNewPrivateVariable:String):void
{
privateVariable = aNewPrivateVariable;
}

/**
* Access my privateVariable.
*
* @return My private variable.
*/
public override function get privateVariableA():String
{
return privateVariable;
}

/**
* @private
*/
public override function set privateVariableA(aNewPrivateVariable:String):void
{
privateVariable = aNewPrivateVariable;
}

/**
* Provide my privateVariable2.
*
* @return My private variable 2.
*/
public override function get privateVariableB():String
{
return privateVariable2;
}
/*
// This must be commented out because ownPrivateVar cannot be seen because it
// is private in my parent and I don't redefine it like I did with some other
// variables with names common with my parent's private variable names.
public function getParentOwnPrivateVar():String
{
return ownPrivateVar;
}
*/

/**
* Provide protected member defined in my parent's class.
*
* @return My parent's protected data member.
*/
public override function getProtectedVariable():String
{
return "Parent: " + someProtectedVariable;
}

/**
* Provide protected member defined in my parent's class.
*
* @return My parent's protected data member.
*/
public override function get protectedVariable():String
{
return "Parent: " + someProtectedVariable;
}
}
}


The third code listing (AccessorsRevealed.mxml) shows the code that invokes methods and accessor functions in the parent and child classes.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flash.display="flash.display.*"
width="250" height="250"
applicationComplete="tryThem();">

<mx:Script>
<![CDATA[
/**
* To compile solely this Flex application from the command line, use
* mxmlc -debug=true -strict=true AccessorsRevealed.mxml
*
* This application demonstrates ActionScript accessors and contrasts them
* with regular ActionScript properties and methods. It demonstrates what
* child classes can and cannot override. The application also demonstrates
* two approaches for introspection on an ActionScript class
* (flash.utils.describeType and mx.utils.ObjectUtil).
*/

import AccessorDemonstrator; // Class with accessor examples.
import ChildAccessorDemonstrator; // Child Class overriding properties.

import flash.utils.describeType;
import mx.utils.ObjectUtil;

private const accessorExample:AccessorDemonstrator = new AccessorDemonstrator();
private const childAccessorExample:ChildAccessorDemonstrator =
new ChildAccessorDemonstrator();

private function tryThem():void
{
trace( "------------------------------------------------------" );
trace( "BEGIN PARENT CLASS DATA MEMBER/ACCESSOR/PROPERY ACCESS" );
trace( "------------------------------------------------------" );

/*
// This call commented out because it will NOT compile and mxmlc compiler
// reports this error:
//
// Error: Attempted access of inaccessible property privateVariable
// through a reference with static type AccessorDemostrator.
//
// (this is because of attempt to access a private property directly).
logToTrace( "private variable (directly) = "
+ accessorExample.privateVariable,
getClassName(AccessorDemonstrator) );
*/

logToTrace( "private variable (get method) = "
+ accessorExample.getPrivateVariable(),
getClassNameWithDescribeType(AccessorDemonstrator) );

logToTrace( "public variable (directly) = "
+ accessorExample.publicVariable,
getClassNameWithDescribeType(AccessorDemonstrator) );

accessorExample.privateVariableA = "Private Property";
logToTrace( "private variable (accessor) = "
+ accessorExample.privateVariableA,
getClassNameWithObjectUtil(AccessorDemonstrator) );

/*
// This section is commented out because no 'set' accessor was provided for
// this property. If this was not commented out, the following compiler
// error message would be reported:
//
// Error: Property is read-only.
//
accessorExample.privateVariableB = "Another Private Property";
*/
logToTrace( "private variable 2 (accessor) = "
+ accessorExample.privateVariableB,
getClassNameWithObjectUtil(AccessorDemonstrator) );

logToTrace( "protected variable (get method) = "
+ accessorExample.getProtectedVariable(),
getClassNameWithObjectUtil(AccessorDemonstrator) );

logToTrace( "protected variable (get accessor function) = "
+ accessorExample.protectedVariable,
getClassNameWithObjectUtil(AccessorDemonstrator) );

// Start accessing data members, get and set methods, and get and set
// accessor functions via child class.

trace( "-----------------------------------------------------" );
trace( "BEGIN CHILD CLASS DATA MEMBER/ACCESSOR/PROPERY ACCESS" );
trace( "-----------------------------------------------------" );

logToTrace( "private variable (get method) = "
+ childAccessorExample.getPrivateVariable(),
getClassNameWithDescribeType(ChildAccessorDemonstrator) );

logToTrace( "public variable (public variable directly) = "
+ childAccessorExample.publicVariable,
getClassNameWithDescribeType(ChildAccessorDemonstrator) );

logToTrace( "private variable (child accessor) = "
+ childAccessorExample.privateVariableA,
getClassNameWithDescribeType(ChildAccessorDemonstrator) );

logToTrace( "private variable 2 (child accessor) = "
+ childAccessorExample.privateVariableB,
getClassNameWithObjectUtil(ChildAccessorDemonstrator) );

logToTrace( "parent's own private variable (get method) = "
+ childAccessorExample.getOwnPrivateVariable(),
getClassNameWithObjectUtil(ChildAccessorDemonstrator) );

logToTrace( "parent's own private variable (get accessor function) = "
+ childAccessorExample.ownPrivateVariable,
getClassNameWithObjectUtil(ChildAccessorDemonstrator) );

logToTrace( "parent-defined protected variable (get method) = "
+ childAccessorExample.getProtectedVariable(),
getClassNameWithObjectUtil(ChildAccessorDemonstrator) );

logToTrace( "parent-defined protected variable (get accessor function) = "
+ childAccessorExample.protectedVariable,
getClassNameWithObjectUtil(ChildAccessorDemonstrator) );
}

/**
* Log provided message to trace() with this class's name prefixing the output.
*
* @param aStringToLog String message to be logged to trace().
* @param aClassName Name of class being used related to logged event.
*/
private function logToTrace(aStringToLog:String, aClassName:String):void
{
trace( "AccessorsRevealed (" + aClassName + ") - " + aStringToLog );
}

/**
* Extract name of class from provided Class using
* flash.utils.describeType().@name.
*
* @param aClass Class whose name is desired.
* @return Name of the provided class.
*/
private function getClassNameWithDescribeType(aClass:Class):String
{
return describeType(aClass).@name;
}

/**
* Extract name of class from provided Class using
* mx.utils.ObjectUtil.getClassInfo().
*
* @param aClass Class whose name is desired.
* @return Name of the provided class.
*/
private function getClassNameWithObjectUtil(aClass:Class):String
{
return ObjectUtil.getClassInfo(aClass).name;
}
]]>
</mx:Script>

<mx:HBox id="mainBox">
<mx:Text text="See trace() output." />
</mx:HBox>

</mx:Application>


When the application is compiled and run with the fdb debugger, the following output is displayed (click on image to see larger image):



These examples demonstrate several things:

  1. From an inheritance perspective, using get/set methods work similarly to get/set accessor functions in terms of what the child class accesses related to the parent class.

  2. A child class cannot see its parent's private data members.

  3. A child class can define a private variable with the same name as its parent's private variable without conflict.

  4. A child class cannot redefine a public data member that its parent class has defined as a public data member.



ActionScript Introspection



The MXML code (the third code listing) also demonstrates two different approaches to performing ActionScript object introspection. The first method shown is to use the flash.utils.describeType() and the second method is to use mx.utils.ObjectUtil.getClassInfo(). This isn't directly related to the discussion of get/set accessor functions, but it is interesting and worth noting. It also is another example of the sometimes redundant functionality in the Flash classes (flash.*) and the Flex classes (mx.*).

No comments: