Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 94 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
94
Dung lượng
363,28 KB
Nội dung
Datatypes in the Virtual Zoo | 157 Example 8-4 shows the code for the Apple class, which represents a specific type of food that pets eat. Finally, Example 8-5 shows the code for the Sushi class, which represents a specific type of food that pets eat. public function getName ( ):String { return name; } public function setName (newName:String):void { name = newName; } } } Example 8-4. The Apple class package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES:int = 100; private var wormInApple:Boolean; public function Apple (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random( ) >= .5; setName("Apple"); } public function hasWorm ( ):Boolean { return wormInApple; } } } Example 8-5. The Sushi class package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES:int = 500; public function Sushi (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); Example 8-3. The Food class (continued) 158 | Chapter 8: Datatypes and Type Checking More Datatype Study Coming Up In this chapter, we learned how to use datatypes to help identify and resolve poten- tial problems in a program. In the next chapter, we’ll conclude our general explora- tion of datatypes by studying interfaces. Like classes, interfaces are used to create custom datatypes. setName("Sushi"); } } } Example 8-5. The Sushi class (continued) 159 Chapter 9 CHAPTER 9 Interfaces10 An interface is an ActionScript language construct that defines a new datatype, much as a class defines a datatype. However, whereas a class both defines a datatype and provides the implementation for it, an interface defines a datatype in abstract terms only, and provides no implementation for that datatype. That is, a class doesn’t just declare a bunch of methods and variables, it also supplies concrete behavior; the method bodies and variable values that make the class actually do something. An interface, instead of providing its own implementation, is adopted by one or more classes that agree to provide the implementation. Instances of a class that provides an implementation for an interface belong both to the class’s datatype and to the datatype defined by the interface. As a member of multiple datatypes, the instances can then play multiple roles in an application. Don’t confuse the term interface, as discussed in this chapter, with other uses of the word. In this chapter, “interface” refers to an Action- Script language construct, not a graphical user interface (GUI) or the public API of a class, sometimes also called an interface in general object-oriented programming theory. Unless you’re familiar with interfaces already, theoretical descriptions of them can be hard to follow, so let’s dive right into an example. The Case for Interfaces Suppose we’re creating a logging class, Logger, that reports status messages (“log entries”) for a program as it runs. Many classes receive the Logger’s status messages and respond to them in different ways. For example, one class, LogUI, displays log messages on screen; another class, LiveLog, alerts a live support technician via a net- worked administration tool; yet another class, LogTracker, adds log messages to a database for statistics tracking. To receive log messages, each class defines an update( ) method. To send a message to objects of each interested class, the Logger class invokes the update( ) method. 160 | Chapter 9: Interfaces That all seems logical enough so far, but what happens if we forget to define the update( ) method in the LogUI class? The status message will be sent, but LogUI objects won’t receive it. We need a way to guarantee that each log recipient defines the update( ) method. To make that guarantee, suppose we add a new requirement to our program: any object that wants to receive log messages from Logger must be an instance of a generic LogRecipient class (which we’ll provide) or an instance of one of LogRecipient’s subclasses. In the LogRecipient class, we implement the update( ) method in a generic way—by simply displaying the log message using trace( ): public class LogRecipient { public function update (msg:String):void { trace(msg); } } Now any class that wishes to receive log messages from Logger simply extends LogRecipient and if specialized behavior is wanted, overrides LogRecipient’s update( ) method, providing the desired behavior. For example, the following class, LogTracker, extends LogRecipient and overrides update( ), providing database- specific behavior: public class LogTracker extends LogRecipient { // Override LogRecipient's update( ) override public function update (msg:String):void { // Send problem report to database. Code not shown } } Back in the Logger class, we define a method, addRecipient( ), that registers an object to receive log messages. The basic code for addRecipient( ) follows. Notice that only instances of the LogRecipient class and its subclasses can be passed to addRecipient( ): public class Logger { public function addRecipient (lr:LogRecipient):Boolean { // Code here should register lr to receive status messages, // and return a Boolean value indicating whether registration // succeeded (code not shown). } } If an object passed to addRecipient( ) is not of type LogRecipient, then the compiler generates a type mismatch error. If the object is an instance of a LogRecipient sub- class that doesn’t implement update( ), at least the generic update( ) (defined by LogRecipient) will execute. Sounds reasonable, right? Almost. But there’s a problem. What if a class wishing to receive events from LogRecipient already extends another class? For example, sup- pose the LogUI class extends flash.display.Sprite: public class LogUI extends Sprite { Interfaces and Multidatatype Classes | 161 public function update (msg:String):void { // Display status message on screen, code not shown } } In ActionScript, a single class cannot extend more than one class. The LogUI class already extends Sprite, so it can’t also extend LogRecipient. Therefore, instances of LogUI can’t register to receive status messages from Logger. What we really need in this situation is a way to indicate that LogUI instances actually belong to two datatypes: LogUI and LogRecipient. Enter interfaces! Interfaces and Multidatatype Classes In the preceding section, we created the LogRecipient datatype by creating a LogRecipient class. That approach forces every Logger message-recipient to be an instance of either LogRecipient or a LogRecipient subclass. To loosen that restriction, we can define the LogRecipient datatype by creating a LogRecipient interface rather than a LogRecipient class. That way, instances of any class that formally agrees to provide an implementation for update( ) can register for log messages. Let’s see how this works. Syntactically, an interface is simply a list of methods. For example, the following code creates an interface named LogRecipient that contains a single method, update( ). (Notice that, like classes, interfaces can be defined as either public or internal.) public interface LogRecipient { function update(msg:String):void; } Once an interface has been defined, any number of classes can use the keyword implements to enter into an agreement with it, promising to define the methods it contains. Once such a promise has been made, the class’s instances are considered members of both the class’s datatype and the interface’s datatype. For example, to indicate that the LogUI class agrees to define the method update( ) (defined by the LogRecipient interface), we use the following code: class LogUI extends Sprite implements LogRecipient { public function update (msg:String):void { // Display status message on screen, code not shown } } Instead of extending the LogRecipient class, the LogUI class extends Sprite and implements the LogRecipient interface. Because LogUI implements LogRecipient,it must define an update( ) method. Otherwise, the compiler generates the following error: Interface method update in namespace LogRecipient not implemented by class LogUI. 162 | Chapter 9: Interfaces Because LogUI promises to implement LogRecipient’s methods, LogUI instances can be used anywhere the LogRecipient datatype is required. Instances of LogUI effec- tively belong to two datatypes: LogUI and LogRecipient. Thus, despite the fact that LogUI extends Sprite, LogUI instances still belong to the LogRecipient type and can be passed safely to Logger’s addRecipient( ) method. (Wow, Ron, that’s amazing! It’s a pasta maker and a juicer!) Compiler errors are the key to the entire interface system. They guarantee that a class lives up to its implementation promises, which allows external code to use it with the confidence that it will behave as required. That confidence is particularly important when designing an application that will be extended by another developer or used by third parties. Now that we have a general idea of what interfaces are and how they’re used, let’s get down to some syntax details. Interface Syntax and Use Recall that an interface defines a new datatype without implementing any of the methods of that datatype. Thus, to create an interface, we use the following syntax: interface SomeName { function method1 (param1:datatype, paramn:datatype):returnType; function method2 (param1:datatype, paramn:datatype):returnType; function methodn (param1:datatype, paramn:datatype):returnType; } where SomeName is the name of the interface, method1, methodn are the methods in the interface, param1:datatype, paramn:datatype are the parameters of the meth- ods, and returnType is the datatype of each method’s return value. In interfaces, method declarations do not (and must not) include curly braces. The following method declaration causes a compile-time error in an interface because it includes curly braces: function method1 (param:datatype):returnType { } The error generated is: Methods defined in an interface must not have a body. All methods declared in an interface must not include an access-control modifier. Variable definitions are not allowed in ActionScript interfaces; neither can interface definitions be nested. However, interfaces can include get and set methods, which can be used to simulate variables (from the perspective of the code using the meth- ods). Like class definitions, interface definitions can be placed directly within a package statement or outside of any package statement, but nowhere else. Interface Syntax and Use | 163 As we saw in the preceding section, a class that wishes to adopt an interface’s datatype must agree to implement that interface’s methods. To form such an agree- ment, the class uses the implements keyword, which has the following syntax: class SomeName implements SomeInterface { } In the preceding code, SomeName is the name of the class that promises to implement SomeInterface’s methods, and SomeInterface is the name of the interface. The SomeName class is said to “implement the SomeInterface interface.” Note that implements must always come after any extends clause that might also be present. Furthermore, if you specify a class instead of an interface after the implements key- word, the compiler generates this error: An interface can only extend other interfaces, but ClassName is a class. The class SomeName must implement all methods defined by SomeInterface, otherwise a compile-time error such as the following occurs: Interface method methodName in namespace InterfaceName not implemented by class ClassName. The implementing class’s method definitions must be public and must match the interface’s method definitions exactly, including number of parameters, parameter types, and return type. If any of those aspects differs between the interface and the implementing class, the compiler generates the following error: Interface method methodName in namespace InterfaceName is implemented with an incompatible signature in class ClassName. A class can legally implement more than one interface by separating interface names with commas, as follows: class SomeName implements SomeInterface, SomeOtherInterface { } in which case, instances of the class SomeName belongs to all three of the following datatypes: SomeName, SomeInterface, and SomeOtherInterface. If a class implements two interfaces that define a method by the same name, but with different signatures (i.e., method’s name, parameter list, and return type), the compiler generates an error indicating that one of the methods was not implemented properly. If, on the other hand, a class implements two interfaces that define a method by the same name and with the exact same signature, no error occurs. The real question is whether the class can provide the services required by both interfaces within a single method definition. In most cases, the answer is no. Once an interface has been implemented by one or more classes, add- ing new methods to it will cause compile-time errors in those imple- menting classes (because the classes won’t define the new methods)! Hence, you should think carefully about the methods you want in an interface and be sure you’re confident in your application’s design before you commit it to code. 164 | Chapter 9: Interfaces If a class declares that it implements an interface, but that interface cannot be found by the compiler, the following error occurs: Interface InterfaceName was not found. Interface Naming Conventions Like classes, interfaces should be named with an initial capital letter so they’re easy to identify as datatypes. Most interfaces are named after the additional ability they describe. For example, suppose an application contains a series of classes that repre- sent visual objects. Some of the objects can be repositioned; others cannot. In our design, objects that can be repositioned must implement an interface named Moveable. Here is a theoretical ProductIcon class that implements Moveable: public class ProductIcon implements Moveable { public function getPosition ( ):Point { } public function setPosition (pos:Point):void { } } The interface name, Moveable, indicates the specific capability that the interface adds to a class. An object might be a piece of clip art or a block of text, but if it imple- ments Moveable, it can be repositioned. Other similar names might be Storable, Killable,orSerializable. Some developers also preface interface names with an “I,” as in IMoveable, IKillable, and ISerializable. Interface Inheritance As with classes, an interface can use the extends keyword to inherit from another interface. For example, the following code shows an interface, IntA, that extends another interface, IntB. In this setup, interface IntB is known as the subinterface, and interface IntA is known as the superinterface. public interface IntA { function methodA ( ):void; } public interface IntB extends IntA { function methodB ( ):void; } Classes that implement interface IntB must provide definitions for both methodA( ) and methodB( ). Interface inheritance lets us define a type hierarchy much as we would with class inheritance, but without accompanying method implementations. ActionScript interfaces also support multiple interface inheritance; that is, an inter- face can extend more than one interface. For example, consider the following three interface definitions: public interface IntC { function methodC ( ):void; } Another Multiple-Type Example | 165 public interface IntD { function methodD ( ):void; } public interface IntE extends IntC, IntD { function methodE ( ):void; } Because IntE extends both IntC and IntD, classes that implement interface IntE must provide definitions for methodC( ), methodD( ), and methodE( ). Marker Interfaces Interfaces need not contain any methods at all to be useful. Occasionally, empty interfaces, called marker interfaces, are used to “mark” (designate) a class as having some feature. Requirements for the marked classes (classes implementing the marker interface) are provided by the documentation for the marker interface. For example, the Flash runtime API includes a marker interface, IBitmapDrawable, which desig- nates a class as eligible for drawing into a BitmapData object. The BitmapData class will draw only those classes that implement IBitmapDrawable (even though IBitmapDrawable does not actually define any methods). The IBitmapDrawable interface is simply used to “approve” a given class for drawing into a bitmap. Here’s the source code for the IBitmapDrawable interface: package flash.display { interface IBitmapDrawable { } } Another Multiple-Type Example In our earlier logging example, we learned that a class can inherit from another class while also implementing an interface. Instances of the subclass belong to both the superclass’s datatype and the interface’s datatype. For example, instances of the ear- lier LogUI class belonged to both the Sprite and LogRecipient datatypes because LogUI inherited from Sprite and implemented LogRecipient. Let’s take a closer look at this important architectural structure with a new example. The following discussion requires a prior knowledge of arrays (ordered lists of values), which we haven’t covered yet. If you are new to arrays, you should skip this section for now and return to it after you have read Chapter 11. Suppose we’re creating an application that stores objects on a server via a server-side script. Each stored object’s class is responsible for providing a method, serialize( ), that can return a string-representation of its instances. The string representation is used to reconstitute a given object from scratch. 166 | Chapter 9: Interfaces One of the classes in the application is a simple Rectangle class with width, height, fillColor, and lineColor instance variables. To represent Rectangle objects as strings, the Rectangle class implements a serialize( ) method that returns a string of the following format: "width=value|height=value|fillColor=value|lineColor=value" To store a given Rectangle object on the server, we invoke serialize( ) on the object and send the resulting string to our server-side script. Later, we can retrieve that string and use it to create a new Rectangle instance matching the original’s size and colors. To keep things simple for this example, we’ll presume that every stored object in the application must store only variable names and values. We’ll also presume that no variable values are, themselves, objects that would need serialization. When the time comes to save the state of our application, an instance of a custom 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. Here’s an excerpt from the StorageManager class that shows the method an object uses to register for storage—addObject( ) (notice that only instances belonging to the Serializable type can be passed to addObject( )): package { public class StorageManager { public function addObject (o:Serializable):void { } } } The Serializable datatype is defined by the interface Serializable, which contains a single method, serialize( ), as follows: package { public interface Serializable { function serialize( ):String; } } To handle the serialization process, we create a class, Serializer, which implements Serializable. The Serializer class provides the following general methods for serializ- ing any object: setSerializationObj( ) Specifies which object to serialize setSerializationVars( ) Specifies which of the object’s variables should be serialized [...]... & 4 // Check whether the expression "3" is not equal to // the expression 3 This code compiles in // standard mode only "3" === 3 // Check whether the expression "3" is equal to // the expression 3 This code compiles in // standard mode only "3" === 3 // Check whether the expression 3 is not equal to // the expression 3 3 != 3 // Check whether the expression "hi" is equal to // the expression "hello"... result i // Increase i by 1, and return the result ++i // Decrease i by 1, and return i i // Increase i by 1, and return i i++ // Create an XML element named BOOK Essential ActionScript 3. 0 Example 180 Precedence 14 14 13 13 13 12 12 11 11 11 Operator typeof void * / % + – > >>> Performs a bitwise unsigned right shift Performs a bitwise signed right shift Performs a bitwise left shift... "Vancouver", "Waterloo"] // Set the value of the array's fourth element cities [3] = "Hamburg"; // cities becomes ["London", "Montreal", "Vancouver", "Hamburg"] // Set the value of the array's third element cities[2] = 2 93. 3; // Notice that the datatype change is not a problem // cities becomes ["London", "Montreal", 2 93. 3, "Hamburg"] Note that we can use any nonnegative numeric expression as the index... operators not covered in this book, see Adobe’s ActionScript Language Reference Statements Statements are one kind of directive, or basic program instruction, consisting of a keyword (command name reserved for use by the ActionScript language) and, typically, a supporting expression Table 10-1 lists ActionScript s statements, their syntax, and purpose Table 10-1 ActionScript statements Statement Usage Description... 52 // Calculate remainder of 14 divided by 4 14 % 4 // Calculate 30 divided by 5 30 / 5 // Calculate four times six 4 * 6 var o:Object = new Object( ); o.a = 10; // Compare undefined to the value of o.a if (o.a == void) { trace("o.a does not exist, or has no value"); } // Retrieve string description of 35 's type typeof 35 Example 181 Checks if an object has a specified public instance variable... ["London", "Tokyo", 2 93. 3, "Hamburg"] Determining the Size of an Array All arrays come with an instance variable named length, which indicates the current number of elements in the array (including undefined elements) To access an array’s length variable, we use the dot operator, like so: theArray.length Here are a few examples: var list:Array = [34 , 45, 57]; trace(list.length); // Displays: 3 var words:Array... right operand Multiplies two numbers Returns the value undefined Returns a simple string description of various types of objects Used for backwards compatibility with ActionScript 1.0 and ActionScript 2.0 only Description Table 10-2 ActionScript operators (continued) // Shift 8 one bit to the right, filling // vacated bits with zeros 8 >>> 1 // Shift 8 one bit to the right 8 >> 1 // Shift 9 four bits... More Essentials Coming Having covered classes, objects, inheritance, datatypes, and interfaces, we’ve now finished our study of the basic concepts of object-oriented programming In the remainder of Part I, we’ll explore a variety of other fundamental topics in ActionScript But the object-oriented concepts we’ve studied will never be far behind Object-oriented programming is the true foundation of ActionScript. .. performed first in an expression with multiple operators For example, when multiplication and addition occur in the same expression, multiplication is performed first: 4 + 5 * 6 174 | // Yields 34 , because 4 + 30 = 34 Chapter 10: Statements and Operators The expression 4 + 5 * 6 is evaluated as “4 plus the product of 5 * 6” because the * operator has higher precedence than the + operator Similarly, when... 2; // Shift n's bits two places to the left n . example of its use: var r:Rectangle = new Rectangle(0xFF 000 0, 0x 000 0FF); r.setSize( 10, 15); // Displays: lineColor=255|fillColor=167116 80| width= 10| height=15 trace(r.serialize( )); If a class would. DEFAULT_CALORIES:int = 500 ; public function Sushi (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); Example 8 -3. The Food. reserved for use by the ActionScript language) and, typically, a supporting expression. Table 10- 1 lists ActionScript s statements, their syntax, and purpose. Table 10- 1. ActionScript statements Statement