8.4 Multiple Type Inheritance with Interfaces

 <  Day Day Up  >  

In our earlier order-form example, we learned that a class can inherit from another class while also belonging to a separate datatype defined by an interface. Our earlier OrderUI class inherited from MovieClip but also belonged to the OrderListener datatype because it implemented the OrderListener interface. That architectural pattern is one of the more common and powerful uses of interfaces ”one in which a class belongs to multiple datatypes without inheriting from multiple classes. Let's take a closer look at this pattern with a new example (source code available at: http:// moock .org/eas2/examples).

Suppose we're creating an application that stores objects on disk, either via a server-side script or a SharedObject instance. Each stored object is responsible for providing a method, serialize( ) , that returns a string representation of the object. The string representation can be used to reconstitute the object from scratch. For example, a Rectangle class with width and height properties might provide a serialize( ) method that returns the string "width= w height= h " (where w and h are the values of the width and height properties). Later, the string "width= w height= h " is retrieved and used to create a new Rectangle instance of the original size . To keep things simple for this example, we'll presume that every object must store only property names and values and that no property values are, themselves , objects that would need serialization.

When the time comes to save the state of our application, the StorageManager class performs the following tasks :

  • Gathers objects for storage

  • Converts each object to a string (via serialize( ) )

  • Transfers the objects to disk

In order to guarantee that every stored object can be serialized (i.e., converted to a string), the StorageManager class rejects any instances of classes that do not belong to the Serializable datatype. The Serializable datatype is defined by the interface Serializable , which contains a single method, serialize( ) , as follows :

 interface Serializable {   function serialize( ):String; } 

Classes that support serialization as described are said to "implement Serializable ." To handle generic serialization cases, in which property names and values are retrieved and converted to strings, we create a class, Serializer , which implements Serializable . The Serializer class has the following methods :


setSerializationProps( )

Specifies which properties of the object, as specified by setSerializationObj( ) , to serialize, as an array


setSerializationObj( )

Specifies which object to serialize


setRecordSeparator( )

Specifies the string to use as a separator between properties


serialize( )

Returns a string representing the object

Here's the class listing for Serializer :

 class Serializer implements Serializable {   private var serializationProps:Array;   private var serializationObj:Serializable;   private var recordSeparator:String;   public function Serializer ( ) {     setSerializationObj(this);   }   public function setSerializationProps (props:Array):Void {     serializationProps = props;   }   public function setSerializationObj (obj:Serializable):Void {     serializationObj = obj;   }      public function setRecordSeparator (rs:String):Void {     recordSeparator = rs;   }   public function serialize ( ):String {     var s:String = "";     for (var i:Number = serializationProps.length; --i >= 0; ) {       s += serializationProps[i]          + "=" + String(serializationObj[serializationProps[i]]);       if (i > 0) {         s += recordSeparator;       }     }     return s;   } } 

To use the Serializer class's serialization services, a class can simply extend Serializer . By extending Serializer directly, the extending class inherits both the Serializable interface and the Serializer class's implementation of that interface.

It is common to define a class that implements a particular interface and then extend that class (i.e., create subclasses that use it). Subclassing in this way allows you to separate a datatype's interface from its implementation. The subclasses use the implementation via inheritance but other, unrelated classes can still choose to implement the interface directly, supplying their own custom behavior.


For example, the following code shows a Point class that defines x and y properties, which need to be serialized. The Point class extends Serializer , allowing it to use Serializer 's services directly.

 // The   Point   class class Point extends Serializer {   private var x:Number;   private var y:Number;   public function Point (x:Number, y:Number) {     super( );     setRecordSeparator(",");     setSerializationProps(["x", "y"]);     this.x = x;     this.y = y;   } } 

Code that wishes to save a Point instance to disk simply calls serialize( ) on that instance, as follows:

 var p:Point = new Point(5, 6); trace(p.serialize( ));  // Displays: y=6,x=5 

Note that the Point class does not implement Serializable directly. It extends Serializer , which in turn implements Serializable .

The Point class does not extend any other class, so it's free to extend Serializer . However, if a class wants to use Serializer but already extends another class, it must use composition instead of inheritance. That is, rather than extending Serializer , the class implements Serializable directly, stores a Serializer object in an instance property, and forwards serialize( ) method calls to that object. For example, here's a Rectangle class that extends a Shape class but uses Serializer via composition (refer specifically to the sections in bold):

 // The   Shape   superclass class Shape {   private var target:MovieClip;   private var depth:Number;   public function Shape (t:MovieClip, d:Number) {     target = t;     depth = d;   } }  // The   Rectangle   subclass implements Serializable directly  class Rectangle extends Shape  implements Serializable  {   private var width:Number = 0;   private var height:Number = 0;   private var serializer:Serializer;   public function Rectangle (t:MovieClip, d:Number) {     super(t, d);  // Here is where the composition takes place   serializer = new Serializer( );   serializer.setRecordSeparator("");   serializer.setSerializationProps(["height", "width"]);   serializer.setSerializationObj(this);  }   public function setSize (w:Number, h:Number):Void {     width = w;     height = h;   }   public function getArea ( ):Number {     return width * height;   }   public function draw ( ):Void {     var container_mc:MovieClip = target.createEmptyMovieClip("rect"                                                               + depth,                                                              depth);     container_mc.clear( );     container_mc.lineStyle(1, 0x000000);     container_mc.moveTo(0, 0);     container_mc.beginFill(0xFFFFFF, 100);     container_mc.lineTo(width, 0);     container_mc.lineTo(width, height);     container_mc.lineTo(0, height);     container_mc.lineTo(0, 0);     container_mc.endFill( );   }   public function serialize ( ):String {  // Here is where the Rectangle class forwards the   serialize( )     // invocation to the   Serializer   instance stored in   serializer     return serializer.serialize( );  } } 

As with the Point class, code that wishes to store a Rectangle instance simply invokes serialize( ) on that instance. Through composition, the invocation is forwarded to the Serializer instance stored by the Rectangle class. Here is an example of its use:

 var r:Rectangle = new Rectangle(this, 1); r.setSize(10, 20); trace(r.serialize( ));  // Displays: width=10height=20 

If a class would rather implement its own custom serialize( ) method instead of using the generic one provided by Serializer , then the class should simply implement the Serializable interface directly, providing the serialize( ) method definition and body itself.

Separating the Serializable datatype's interface from its implementation allows any class to flexibly choose from among the following options when providing an implementation for the serialize( ) method:

  • Extend Serializer

  • Use Serializer via composition

  • Provide its own serialize( ) method directly

If the class does not already extend another class, it can extend Serializer (this option means the least work). If the class already extends another class, it can still use Serializer via composition (this option is the most flexible). Finally, if the class needs its own special serialization routine, it can implement Serializable directly (this option means the most work but may be required by the situation at hand).

This flexibility led Sun to formally recommend that, in a Java application, any class that is expected to be subclassed should be an implementation of an interface. As such, it can be subclassed directly, or it can be used via composition by a class that inherits from another class. Sun's recommendation is also sensible for large-scale ActionScript applications.

Figure 8-1 shows the generic structure of a datatype whose implementation can be used via either inheritance or composition.

Figure 8-1. Multiple datatype inheritance via interfaces
figs/as2e_0801.gif

Figure 8-2 shows the structure of the specific Serializable , Point , and Rectangle example.

Figure 8-2. Multiple datatype inheritance Serializable example
figs/as2e_0802.gif

 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net