• Aucun résultat trouvé

Using Interfaces and Abstract Classes in ActionScript 3.0

Dans le document ActionScript 3.0 Design Patterns (Page 49-55)

Inheritance can also be linked to two other structures;interfacesandabstract classes.

However, the connection between the interface structure (a statement in Action-Script 3.0) or the abstract class and inheritance is a bit different from the one with theclass structure we’ve examined thus far.

Interface constructs

First of all, in this context,interfacerefers to an ActionScript 3.0 statement and not the object interface discussed in the “Encapsulation and Design Patterns” section.

While a class can be said to be an abstraction of an object, an interface is an abstraction of methods. They are widely used in design patterns. Beginning in Chapter 5 with the adapter pattern, you will see interfaces at work in several of the other design patterns.

To begin to see what an interface does, a simple example illustrates the use of one.

However, once you start seeing how they’re used in design patterns, you will better see their utility. Example 1-17 shows how a typical interface is created. The applica-tion is made up of Example 1-17 to Example 1-20.

The first thing we’ll do is to make our interface. As you can see in Example 1-17, the single function is quite simple and devoid of content. It does have a name, parame-ter, and return type, but note that the function is only a single line.

Each implementation of the interface must have exactly the same structure in all of the methods in the interface, and if anything is not the same, you’ll get an error mes-sage. As long as the signature for the methods is the same, everything should work fine. Example 1-18 is the first implementation of the of theBandFace interface.

Example 1-17. BandFace.as package

{

//Interface

public interface BandFace {

function playInstrument(strum:String):void;

} }

Example 1-18. Guitar.as package

{

public class Guitar implements BandFace {

public function Guitar( ) {}

Looking at Example 1-19 and theBongoclass, at first you may think that the method is built incorrectly. It’s wholly different from theGuitar class in its details, but the method’s signature is identical.

Remember, when working with interfaces, the number of methods in an interface can be many or few, but as long as each implementation of the interface includes every method in the interface and maintains the structure, everything works fine.

You may be wondering where the inheritance is. Given the fact that you must build all the interface’s methods, it looks more like a customization than an inheritance.

However, the BandFace subclasses all inherit its interfaces. So essentially, the sub-classes inherit the interface but not the implementation.

Finally, to test the application, theMakeSoundclass, listed in Example 1-20, tests both classes and their very different constructions of the single method from theBandFace public function playInstrument(strum:String):void

{

trace("Playing my air "+ strum);

} } }

Example 1-19. Bongo.as package

{

import flash.media.Sound;

import flash.media.SoundChannel;

import flash.net.URLRequest;

public class Bongo implements BandFace {

public function Bongo( ){}

private var sound:Sound;

private var playNow:SoundChannel;

private var doPlay:URLRequest;

public function playInstrument(strum:String):void {

sound=new Sound( );

doPlay=new URLRequest(strum);

sound.load(doPlay);

playNow=sound.play( );

} } }

Example 1-18. Guitar.as (continued)

interface. You’ll need an MP3 file namedbongo.mp3(use any handy MP3 file) saved in the same folder as theMakeSound.as file.

Note that both instances,guitarandbongo, are typed to the supertype,BandFace, and not to either theGuitaror the Bongoclasses. This practice follows the first principle of reusable object-oriented design:Program to an interface, not an implementation.

The purpose of doing so is to maintain flexibility and reusability. This principle will be fully explored elsewhere in this chapter and in Chapter 8, but for now, just note that fact.

A word about the interface and abstract class naming conventions used in this book: with the focal point of this book on object-oriented programming and design patterns, we use naming conventions that best reflect and clarify the structure of the different design patterns. As a result, we don’t always follow some of the naming conventions. One convention is to name interfaces beginning with a capitalI. So follow-ing that convention, the BandFaceinterface would have been named IBandFace. Where using the I does not interfere with clarifying a design pattern structure, we use it, but where we have several inter-faces in a design pattern, often we forego that convention. Another convention is to name abstract classes usingAbstract+Something. So, AbstractHorses would be a name for a class you’d otherwise name Horses. Again, our focus on revealing structure supersedes using these conventions. We differentiate abstract from concrete classes using comments in the code. Throughout the book, however, we attempt to be as clear as possible in naming the different classes. You may want to adopt some of these more common conventions, once you better understand them, to aid in keeping your code clear.

Example 1-20. MakeSound.as package

{

import flash.display.Sprite;

public class MakeSound extends Sprite {

private var guitar:BandFace;

private var bongo:BandFace;

public function MakeSound( ):void {

guitar=new Guitar( );

guitar.playInstrument("Gibson");

bongo=new Bongo( );

bongo.playInstrument("bongo.mp3");

} } }

Abstract classes and overriding inheritance

As you become familiar with design patterns, you’ll see more and more use of inter-faces and its close cousin theabstract class. In ActionScript 3.0 the abstract class is a little problematic because no class can be actually defined as abstract. While you can use thepublicstatement to make a class public, ActionScript 3.0 (and ECMAScript) chose not to include abstract classes in the language, as does Java.

However, you can create an abstract class in ActionScript 3.0. All you have to do is create a regular class and treat it as an abstract class. Like interfaces, abstract classes can have abstract methods that are not directly implemented. Rather, abstract classes are subclassed and any abstract methods are overridden and implemented very much like methods are in using interfaces. However, abstract classes can have implemented methods as well; so when you need both abstract and implemented methods, abstract classes are key to such design patterns as the Factory Method (Chapter 2), Decorator (Chapter 4), and the Composite (Chapter 6), as well as others.

You know from inheritance that when one class subclasses another, the subclass inherits the methods of the superclass. With an abstract class, you do not implement the class but instead subclass it, and then implement the subclass and its methods.

Because of the abstract nature of at least some of the methods in the abstract class, you must override them. Using the override statement, an overridden class main-tains its signature but can have its own unique details. You must be sure that the name, number, type of parameters, and the return type are the same. In other words, when you override a method from an abstract class, you treat the method exactly the same as aninterface method.

Example 1-21 through Example 1-23 make up an application that illustrates how an abstract class works. The abstract class has two methods; a concrete one and an abstract one.

The subclass inherits the methods and interface from the abstract class, and it pro-vides details for the abstract method by overriding it. However, the subclass leaves the concrete class as is and does not attempt to instantiate the superclass.

Example 1-21. AbstractClass.as package

{

//Abstract class

public class AbstractClass {

function abstractMethod( ):void {}

function concreteMethod( ):void {

trace("I'm a concrete method from an abstract class") }

} }

When the application finally implements the methods originating in the abstract class, it does so by programming to the interface (AbstractClass) but instantiates through the subclass (Subclass). So the instance, doDemo, is typed asAbstractClass but instantiated asSubclass.

The following shows what appears in the Output window when you test the program:

This is the overidden abstract method I'm a concrete method from an abstract class

At this point you may be scratching your head wondering why you should go through this kind of convolution to do what you could do with a non-abstract class.

Instead of subclassing the abstract class, overriding one of the methods, and then implementing the subclass, we could have just written both methods the way we wanted them in the first place and not have to override anything. The next section attempts to answer that question.

Example 1-22. Subclass.as package

{

//Subclass of Abstract class

public class Subclass extends AbstractClass {

override function abstractMethod( ):void {

trace("This is the overidden abstract method");

} } }

Example 1-23. ImplementSub.as package

{

//Implement Subclass of Abstract class import flash.display.Sprite;

public class ImplementSub extends Sprite {

private var doDemo:AbstractClass;

public function ImplementSub( ) {

doDemo=new Subclass( );

doDemo.abstractMethod( );

doDemo.concreteMethod( );

} } }

Why use interfaces and abstract classes?

To understand why to use interfaces and abstract classes, we need to consider the whole purpose of design patterns. It’s the ability to reuse object-oriented software.

We’ve been using fairly simple examples to help clarify the concepts. However, typi-cal software is usually far more complex, and the algorithms more sophisticated. Once you complete a project, you’re likely to have to make a change. The larger and more complexthe project, the more difficult it is to reuse the assets you’ve developed, main-tain the interconnections and generally make any kind of change without unraveling the whole thing or introducing code that may make future change impossible.

To illustrate what this means, consider the application in Examples 1-21 to 1-23 in the previous section. Multiply the complexity of the classAbstractClassby a factor of 10.

Do the same to the number of subclasses. Now you’re dealing with some serious com-plexity. Next, consider that you need to maintain the functionality of the class, Subclass, and yet change the abstract method in theAbstractClassfor a new function-ality. Also, you have to maintain all of the interfaces and connections you’ve built.

Because you used an abstract class, you can create a new subclass that overrides the abstract function and uses it in a different way. To see how this all works, we’ll make a new subclass of the AbstractClass and change the abstract method. We’re not changing anything else in the entire application, so we don’t have to worry about everything working together because we can separate our new subclass and method and only implement them where needed. Other subclasses of theAbstractClassare unaffected. Examples 1-24 and 1-25 show the two new classes created to make the change.

Note that instead of having a singletrace statement, Example 1-24 uses three. This modification simulates a more complexchange. However, Example 1-25, which implements the application, is identical to Example 1-23.

Example 1-24. SubclassChange.as package

{

//A New Subclass of Abstract class with a change public class SubclassChange extends AbstractClass {

override function abstractMethod( ):void {

trace("This is the new abstractMethod!!") trace("Made just one little important change.");

trace("But this still works just fine!");

} } }

All the changes are revealed in the line,

doDemo.abstractMethod( );

Even though the line is the same as Example 1-23, the output window reveals that a good deal has changed:

This is the new abstractMethod!!

Made just one little important change.

But this still works just fine!

I'm a concrete method from an abstract class

What’s important isnot that it made the changes. The point that you need to con-sider carefully is the fact that such changes can be made without disrupting the other elements in your application that you have reused. So without having to rewrite the entire application, you can keep its functionality while making necessary changes.

Polymorphism

The root of polymorphism is a word that could easily replace it to describe the pro-cess—metamorphosis. The term is from the Greekmetamorphoun, meaning to trans-form. One definition describes metamorphosis as a magic-like transformation—

something a sorcerer would do. If you like, think of polymorphism as giving the pro-grammer the power of a sorcerer.

Dans le document ActionScript 3.0 Design Patterns (Page 49-55)