Using Polymorphism


Polymorphism is a complex-sounding word. However, the concept it represents is not very complex now that you understand what an interface is. The basic idea of polymorphism is that any class that implements an interface looks a lot like any other class that implements that same interface; any object that implements an interface can stand in when that interface is expected.

If you haven't used interfaces up to now, chances are you haven't leveraged the power of polymorphism yet. However, once you understand polymorphism, you'll quickly see how flexible it makes your code. Here's an example. First the interface is defined as follows:

package {    public interface ISearchable {       function search(searchTerm:String):Array;    } }


Next we can define a class called Library which implements ISearchable:

package {    public class Library implements ISearchable {       private var _books:Array;       public function Library(books:Array) {          _books = books;       }       public function search(searchTerm:String):Array {          var results:Array = new Array();          for(var i:int = 0; i < _books.length; i++) {             if(_books[i].title.indexOf(searchTerm) != - 1) {                // Assume that each item in the array                 // is a custom Book type that has a                 // clone() method.                results.push(_books[i].clone());             }          }          return results;       }    } }


We can also define a Help class that implements the ISearchable interface as well.

package {    public class Help implements ISearchable {       private var _helpIndex:Object;       public function Help() {         // Assume the _helpIndex is populated by data loaded         // from an XML file, and that each item in          // _helpIndex is an array where the key is          // a search term.       }       public function search(searchTerm:String):Array {          if(_helpIndex[searchTerm] != null) {             return _helpIndex[searchTerm];          }          else {           return new Array();          }       }    } }


Now, even though Library and Help are different classes with different implementations, either can be used any time ISearchable is expected. For example, the following assigns a new Library instance to searchCollection.

var searchCollection:ISearchable = new Library(books);


However, note that you can also substitute a Help instance. And furthermore, you can make that substitution at runtime.

searchCollection = new Help();


We'll next take a look at this concept in more detail.

Differentiating Between Type and Class

When you've declared a variable in the past, you most likely declared the variable so that the type was identical to the class for which you planned to instantiate an object to assign to the variable. For example, if you wanted to declare a variable to which you could assign an instance of class Vegetable, you probably declared the variable in the following fashion:

var item:Vegetable;


Although there's nothing inherently wrong with the preceding code, there is an inherent inflexibility in that way of declaring a variable. Because you've declared item as type Vegetable, you can assign to it only those objects that are instances of class Vegetable or its subclasses. Even if class Fruit has the exact same interface as class Vegtable, you cannot assign an instance of class Fruit to item when you declare item as in the preceding example.

To write more flexible code, you have to differentiate between class and type. In the preceding example, Vegetable is both a class and the type. However, there's a correspondence between a type and an interface and between a (concrete) class and an implementation. Although a concrete class defines both an interface and implementation, an interface defines just the interface. Likewise, a class is also a type, but a type does not have to be a class. Types can also be interfaces. By declaring variables with interface types, you create greater flexibility in your code. Consider the following example:

var item:IProduce;


Now that item is declared as type IProduce, we can assign to item any instance of any class that implements IProduce. We're no longer locked into one specific class. If both Fruit and Vegetable implement IProduce then you can assign an instance of either class to item now that it's typed as IProduce.

Making Runtime Decisions

When you declare a variable with a type of a concrete class, you generally are making a compile-time decision as to which implementation to use. That is okay when you know absolutely that you want to use only that one implementation. However, consider an example in which an application uses a fallback plan for network communications. According to the business rules for the application, it must first attempt to communicate using Flash Remoting/AMF. If that does not work, the application next must attempt the communication using an HTTP request that sends and retrieves XML data. And if that does not work, the application next must attempt to make a binary socket connection to a server. In this case, you cannot know at compile time which protocol the application will use for network communications. If everything works correctly, the application will use Flash Remoting/AMF, but if that doesn't work, the application will have to fall back on one of the alternatives. This situation presents a dilemma if you're not programming to interfaces, but it accepts a fairly trivial solution if you are.

In order to solve the dilemma presented by this hypothetical network protocol selection issue you can write three classes (one for each protocol) that implement the same interface that we'll call INetworkProtocol. You can then program to that interface rather than to any one of the specific classes. Doing so allows you to plug in an instance of any of the implementing classes. Even though the implementations are all different, they use the same interface and therefore appear the same from the outside. That allows you to change which protocol you use at runtime.

In order to better understand this concept let's look at some sample code. We'll solve the network protocol issue by creating an interface. Note that this example shows an interface that extends an existing interface (IEventDispatcher). That means that the implementing classes must implement both the INetworkProtocol and the IEventDispatcher methods.

package {    import flash.events.IEventDispatcher;    import flash.net.URLRequest;    public interface INetworkProtocol extends IEventDispatcher {       function setService(service:String):void;       function sendRequest(request:URLRequest):void;       function testConnection():void;    } }


For this example we'll also assume that the following class defines constants we'll use for event names.

package {    import flash.events.Event;    public class NetworkEvent extends Event {       public static const CONNECT:String = "connect";       public static const FAILED:String = "failed";       public static const RESULT:String = "result";       public static const ERROR:String = "error";    } }


Now that we've defined INetworkProtocol, we can define AMFService, XMLService, and BinarySocketService so that they each implement the interface. For the sake of brevity we'll omit the actual class definitions here. What's important is not the implementation in this case, but the fact that they each implement the same interface. The code that decides which protocol to use might look like the following:

package {    import flash.events.EventDispatcher;    public class Service extends EventDispatcher {       private var _serviceURL:String;       private var _service:INetworkProtocol;       private var _services:Array;       private var _hasValidService:Boolean;       public function Service(serviceName:String) {          _services = new Array(new AMFService(),           new XMLService(), new BinarySocketService());          _serviceURL = serviceName;          tryNextService();       }       private function tryNextService():void {          _service = _services.shift();          _service.addEventListener(NetworkEvent.CONNECT,          onConnect);          _service.addEventListener(NetworkEvent.FAILED,          onFailed);          _service.setService(_serviceURL);          _service.testConnection();       }       private function onConnect(event:Event):void {          _hasValidService = true;          _service.addEventListener(NetworkEvent.RESULT,          onResult);          _service.addEventListener(NetworkEvent.ERROR,          onError);       }       private function onFailed(event:Event):void {          if(_services.length > 0) {             tryNextService();          }       }       private function onResult(event:Event):void {          dispatchEvent(event);       }       private function onError(event:Event):void {          dispatchEvent(event);       }       public function sendRequest(request:URLRequest):void {          _service.sendRequest(request);       }    } }


This example is not intended to show a fully-functional, bullet-proof network communication mechanism. What it is intended to demonstrate is how programming to interfaces enables polymorphism. In this case _service is typed as INetworkProtocol. Since AMFService, XMLService, and BinarySocketService all implement INetworkProtocol, it's possible to assign instance of any of those classes to _service.




Advanced ActionScript 3 with Design Patterns
Advanced ActionScript 3 with Design Patterns
ISBN: 0321426568
EAN: 2147483647
Year: 2004
Pages: 132

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