Managing Data in Classes


The latest version of Flash provides some major improvements over how classes have worked in the past within Flash. So far, we've seen class files that would be somewhat more recognizable to programmers from other languages plus some convenient ways of dealing with instantiation and inheritance. This section covers some new features that provide further structure to our classes. By providing rigid structure in a class, it is easier to find errors during development. It's also easier to set up ground rules that keep you programming in a good style that doesn't allow you to raid and pilfer your objects except by the avenues you set up in your planning stage.

Compile-Time Features

The majority of the new features to help you debug an application are checks Flash does at compile-time. We will be looking at ways of indicating to the compiler how certain classes, properties, and methods can be used. When you check the class file's syntax, you're checking permissions by looking at your class files to see whether violations have occurred.

These features do not check run-time code to see if it is consistent with the permissions. The run-time code is the code that you add as actions and event handlers within the .fla. For compile-time checks to be effective, the majority of your code must reside in class files. This distinction of when the checks occur is a bit confusing, so I'll try to illustrate the difference in examples as we go through the features.

Dynamic Classes

Using ActionScript 2.0, we are able to tell classes whether they can create and reference properties that were not specifically set up in the class. By default, classes do not allow properties to be created or used within the methods unless they were set as instance variables . Trying to create or reference a property that wasn't declared in the class first causes a compiler error. If you set a class to be dynamic, the checks the compiler performs are eased to allow for properties to be added at run-time.

The syntax for making a dynamic class is easy. It is only the decision of whether or not to use the keyword that is sometimes difficult. To make a class dynamic, just add the keyword dynamic in front of your class definition like this:

 dynamic class MyClass {      //class definition goes here } 

If you are writing a subclass that extends a dynamic class, your subclass is automatically dynamic even if you don't specify it. However, if your superclass is not dynamic, you can still make the subclass dynamic. The only thing you can't do is have a superclass be dynamic if the subclass is not. This is because your subclass might inherit methods that dynamically add properties not allowed in the subclass.

Let's look at what it would take for a nondynamic class to have a problem. One easy way to do this is to try to define a variable within a method in the class. For instance, if you write the following, Flash gives you a compiler error.

 class MyClass {       function doSomething() {            this.newVariable=1;       } } 

If you run the syntax checker, you get the following error:

 **Error** Line 3: There is no property with the name 'newVariable'. 

If you add dynamic at the beginning, the error won't happen again. In this example, it is important to have an explicit this to make sure that the variable is properly scoped. Whether or not the class was dynamic, without the this , you would still get a compiler error.

Deciding When to Use dynamic

Although dynamic allows you to keep your options open , for the most part, you should plan ahead for what the application will do and account for it within your class definition. By not making a class dynamic, a developer can feel more confident about the flow of his program because the stronger check that the compiler performs catches errors where variables are casually defined.

Tip  

Flash checks classes that the current class is extending when verifying that a property already exists. Only if Flash cannot find the declaration of the variable in the chain of inheritance does Flash generate an error.

That being said, there are times when you need to leave an object dynamic to incorporate new properties at run-time. For instance, if you created a class that you were going to associate with a symbol in the library where you chose not to set up the class to inherit from MovieClip , you would still have access to some of the properties such as _y and _x . In a class that isn't marked as being dynamic, Flash generates an error because it doesn't already know what _y is.

There are other cases involving movie clips that make good cases for dynamic classes. As you attach a movie clip to another movie clip, you are adding new properties to that parent movie clip. If the parent is associated with a class, that class should be set as dynamic.

Before we move on to the next topic, I want to illustrate the difference between run-time checking and compile-time checking. To do this, we need to create an .as file for a class and an .fla in the same directory. For simplicity, let's make it MyClass.as and MyClass.fla . In the class file, add the following (and make sure to save the file):

 class MyClass {       //class definition goes here } 

Earlier, we saw that if we added a function that tried to create a new variable, a compiler error occurred. For now, just know that if we did that again, we would get the same result. In frame 1 of the main timeline in the .fla, type the following:

 classInstance = new MyClass(); classInstance.newVariable=1; trace(classInstance.newVariable); 

If you test the movie, not only do you not get an error, but you see that the new variable was created and returned by trace . That's because ActionScript 2.0 is a bit of an illusion. What is actually happening is that Flash creates a global object with your class in it, in what is really ActionScript 1.0. Because AS1 can't block adding new properties to an object, neither can AS2 after it has been converted.

Typecasting Values

In your class files, you can explicitly type any variable, function, or parameter of the class. Once again, this has no bearing on run-time code, but among your class files, this can detect errors in your code by making sure that what is stored in a variable is the data type expected, and what is sent to a function and returned is also the correct type. Although this only helps minimally for your code, it adds a lot in terms of code readability because you can immediately see what sort of data is being processed in a function without having to track it back to its source.

You can also be assured that other objects are not calling your methods with invalid typed data. In a larger scale development environment where it is no longer one program by one developer, you can be assured that if someone else misunderstands what information your object's methods take, a compiler error warns you.

Typing Properties

To specify the type of variables, let's set up another version of MyClass where we need one variable that will be a string, one that will be a number, and one that will be an array.

 class MyClass {      var myString:String;      var myNumber:Number = 5;      var myArray:Array = [1,2,3]; } 

Directly after each variable name is a colon followed by a data type. The colon is considered the type operator. This doesn't put anything in the variable; it only says that whatever goes into the variable must be of this type (checked at compile-time). After the type declaration, you can operate as usual. If you don't specify a value, the variable is available to be set. (This is a way you can define a variable so that you do not get a compiler error if your class is not dynamic.)

In the number example, the value 5 is stored. Had we put the string "5" as the value, Flash would not have converted this value to a number. Instead, it would have generated an error informing you of a mismatch in data types.

Tip  

If a variable is typed X, it can still accept data that is typed as a subclass of X. The reverse does not hold true, though. Flash generates an error if a variable is typed into one of X's subclasses and you try to place data of type X into it.

In the line with the array, you see that you can create an array as part of the instance variables; however, you cannot use new Array() here because that would generate an error. Flash only allows compile-time constants to be used as the values of instance variables. If we wanted to instantiate an object in the variable, we could place the code in the constructor like this:

 class MyClass {     var myString:String;     var myNumber:Number = 5;     var myArray:Array;     function MyClass (){          myArray = new Array();     } } 
Typing Methods

When you define functions in classes, you can specify which data type each of the arguments received by the function should be. Just as when you are setting the value of a variable, if a function call is made with the wrong type of data, a compiler error is reported .

You can also type the response that the function should return. If your return statement violates the type, a compiler error results. To see this in action, I'm once again re-creating the MyClass class.

 class MyClass {      function additionPhrase(value1:Number, value2:Number):String {           return value1+" + "+value2+" = "+(value1+value2);      } } 

Here we have a function that takes two numbers and makes a small phrase out of them. Once again, we use the colon operator to indicate that we want to set a type for the variables ”in this case, both numbers . The function needs to return a string (the phrase). To do that, we use the colon operator after the function name and argument list.

Public and Private Properties and Methods

A key concept in OOP is the ability to control access to the properties and methods so that other objects cannot come in and rummage through another object, potentially creating bugs along the way. The name for this concept is data hiding .

Hiding here refers to a permission system by which we can set our variables and functions off limits to other objects. Don't confuse this with data security, however. The data that is private is not more secure than the rest of your application.

Think of an object that runs a bank. The customers who come in represent calls made by other objects. The bank is set up to take requests from customers in specific ways. You can walk up to a teller and perform a transaction such as depositing or withdrawing money. After the request has gone to the teller, that teller can carry out actions such as getting money out of an account or putting more money in. The back does not leave its doors open for us to go and directly access the computer system so that we can tell the system how much money we have.

A well-designed program should work the same way. You create public functions that deal with requests to the object, but then those functions call the powerful private functions. Outside objects can only do as much damage to an object as is allowed by the public functions. This is another case of designing a program for a multideveloper environment. You would not want to allow another coder 's object to modify important values of your object unless you knew they were supposed to do that, in which case you would set up a public mechanism for them.

All methods and properties are public by default. Even though you can overtly specify a property or method as public, you are only required to make those that you want to keep private. To do this, let's re-create the MyClass class with a public and private property and method (see Figure 9.4).

click to expand
Figure 9.4: Instances can only access public properties and methods.
 class MyClass {      public var name = "My Name";      private var count = 0;      public function doSomething() {           count++;           reallyDoSomething();      }      private function reallyDoSomething() {           trace("done");      } } 

If anything other than code within the object or subclass tries to access the private elements, an error is generated. Notice that the public function can access the private property and method. In this example, something like count might be used in the condition of a loop. If this variable is made public, another object can inadvertently change how many times a loop occurs.

To test the compiler error, we need to create a new class. Any code we place in MyClass automatically has permission to access the private elements. Let's create MyOtherClass.as.

 class MyOtherClass {      var myClassInstance:MyClass;      function MyOtherClass() {           myClassInstance = new MyClass();           myClassInstance.doSomething()      } } 

In our new class, we are setting up a variable to hold an instance of the MyClass class. Notice that I went ahead and typed it into our custom class. Now we can't use that variable for anything else. In the constructor, I instantiated the MyClass object and then made a call to its public function. All this works without issues.

If we change the function call to the following, a compiler error is generated.

 myClassInstance.reallyDoSomething() 

The error will be as follows :

 Line 5: The member is private and cannot be accessed. [ccc]myClassInstance.reallyDoSomething() 

Implementing Interfaces

When you have a large-scale project requiring different developers to write different objects, the planning phase is critical. During that planning, you must create a list of classes that you need, give these classes names , and decide what public functions this object will need. After you have all these elements nailed down, you can largely assume that what goes on inside the object will be acceptable provided that its public functions do what you expect.

You can verify that each object has the methods it should have and that arguments and return types meet your expectations by setting up an interface. An interface is nothing more than a list of methods and their argument lists (which you can type if you prefer). No implementations of these functions exist in the file.

When the developer starts working on the class, he can tell the class that it is supposed to implement the interface. When you compile the application, if you don't have at minimum the methods listed in the interface with the right number of arguments and data types, a compiler error is generated. Also, if any of the methods from the interface are listed as private, a compiler error occurs.

This concept is called programming by contract . The developer in charge of overseeing the integration of all the classes can write interfaces stating what public methods they expect to exist in the class. The class then fulfills the contract by having the correct methods.

Creating an interface is similar to creating a class. Interfaces need to be in their own .as files with names that match the interface name. Here is a simple example of an interface. It should be written in a file called myInterface.as.

 interface MyInterface{       function methodOne(value1:Number, value2:String);       function mySecondMethod (name:String):String;       function myMethod3():Object; } 

Any class that uses this interface must have these three methods fully defined, and in all cases where type is specified, the types of these functions must agree. To have a class use this interface, you would go to the class file and add the following. (Make sure the interface file and the class file are in the same directory.)

 class MyClass implements MyInterface {      function methodOne(value1:Number, value2:String) {           //something happens      }      function mySecondMethod(name:String):String {           //something happens           return "string"; // must return a string      }      function myMethod3():Object {           //something happens           return {}; // must return an object      } } 

The keyword implements is what loads the interface file and causes Flash to check the contents of the class. If you would like to see the various errors that can occur, try commenting out some of the methods or return lines, or try altering the types associated with the arguments.

Tip  

Interfaces can also be extended like classes. If you want to create a derived interface, create a new interface where you use the keyword extends after the interface name. Then specify the interface that you would like to extend.

Interfaces are only the minimum requirements of a class. You can still add other public functions to the class as well as private functions and properties. In fact, one class can implement more than one interface at a time. To do this, make a comma-separated list of the interfaces that should be implemented, like this:

 class MyClass implements Interface1, Interface2 {       //all the methods specified by these two interfaces must be present here } 
Pseudo Multiple Inheritance

The reason you might want to implement more than one interface at a time is that ActionScript 2 cannot perform multiple inheritance. This means that our Zapper subclass cannot extend both the Alien class and a class like shooters . In this case, shooters is a hypothetical class of methods that any object that needs to fire should have. In the case of our alien game idea, the Zapper alien would need to fire, but the good guy also will most likely need to shoot. The good guy should not be part of the Alien class. In this case, it would be useful for Zapper to simultaneously extend Alien and shooter .

Tip  

If you would like to have a derived class implement an interface, you need to use extends first and then implements .

Because we can't do this, one thing we can do is make an interface of all the methods that any shooters class should have and then have all those classes in an interface. Although it doesn't give us the inheritance benefits like extending does, we can at least make sure that all our disparate shooter types are using consistently named methods and that they process the same types of information.

Compile-Time and Run-Time Features

The next group of features can help you organize and debug your code in much the same way that the compile-time checking features can. The difference is that these features also have an effect at run-time. We are going to look at creating static methods and properties of objects as well as using implicit get and set to control accessing properties of objects.

Using Static Methods and Properties

Static methods and properties are those that you call on the class, not on the instance. An example from Flash's predefined String class would be String.fromCharCode . When you want to call this method, you refer to String , not an instance of the String object. The rest of the string methods are not static. These you call on individual strings. length , for instance, has no meaning unless it is called on an instance. We can't call String.fromCharCode on an instance of a string because this method is creating a new string based on the arguments of the method.

Another example is Math . All of Math 's properties and methods are static. Because the mathematical operations do not change from instance to instance, it makes more sense to call them off the class. By placing the keyword static in front of any property or method in your class, you can achieve a similar result.

Tip  

Keep in mind that instances of the class do not have the method when instantiated. They still need to refer to the class name when calling the function.

As an example, let's create a static property and function as well as a regular property and function like we did with public and private .

 class MyClass {      static var name = "My Name";      var count = 0;      static function acknowledge() {           trace("acknowledged")      }      function doCount() {           trace(++count);      } } 

To access the static elements, you have to call MyClass.name and MyClass.acknowledge . When you use the static property or method, the Flash compiler goes out and looks for the class definition. For nonstatic properties and methods, you refer to the instance name of the object. As a test, try adding the following line in doCount :

 this.acknowledge() 

You get a compiler error because Flash knows that all static methods must be called with the class name, not a reference to the current instance.

Using Implicit Get and Set

In OOP, another concept that is considered good style is prohibiting any of your properties from being public. If an outside object wants to have access to either read or write to the variable, the outside object should call a public getter or setter function. That way, you have greater control over what happens with your properties. You can perform verifications in the setter function that allow you to determine whether you want to allow the variable to be overwritten.

You can set up functions that handle the getting and setting of a property, but to the outside object, it looks like it is simply using a property. Many examples of this are already being used in Flash. In the Array and String objects is a length property that is constantly updated and that can be read but not written. With implicit getter s and setter s, you can set up a function that gets the value of a variable and returns it, but where the setter is not there. That way, no one can change the value from the outside.

Following is a class in which there is an implicit getter for the private property privName that has no setter, as well as a getter and setter that access the properties of the movie clip associated with this class to provide a simplified way of accessing scale.

 class MyClass extends MovieClip {      private var privName = "My Name";      function get pubName() {           return privName;      }      function get scale() {           return _xscale;      }      function set scale(value) {           _xscale = _yscale = value;      } } 

In the case of pubName , the keyword get in front of the function name tells Flash to allow implicit getting by writing code like the following in an .fla that is in the same directory as the class file:

 myClassInstance= new MyClass() trace(myClassInstance.pubName) 

If you try to set a value to this variable, you can use a trace afterward to see that no change occurred to the property because there was no set function.

 myClassInstance= new MyClass() trace(myClassInstance.pubName) myClassInstance.pubName=" Bob" trace(myClassInstance.pubName) 

For both of these traces, you should see My Name as the response.

In the second half of the class definition, you can see that we have two functions named scale . Normally this would be a problem, but because one is marked with get and the other with set , there is no collision between the names. When you use scale to look up a value, it runs the get function, which looks up a property of the movie clip. When you assign a value to scale , it set s two properties of the movie clip. There is no real limitation on what these get and set methods can do, but there is a restriction on what arguments they can receive.

get functions must take no arguments. Because you aren't able to pass in any arguments when looking up a value by implicit get , this should seem self-explanatory. Likewise, when you assign a value by using implicit set , you are sending one value, so your set function must have one and only one argument. If you try to give these functions the wrong number of arguments, a compiler error occurs.

Using Packages

Packages are directories that have class files in them. They allow you to organize similar class files into a common folder. The folder needs to be in one of the paths specified in your preferences for where to look for class files. To use packages, there are only two things you need to do. You need to modify the class name slightly, and you need to make a similar change when you call in the package. Needless to say, with changes having to be made in the class file and everywhere it is called in, choosing to organize files into packages is something that you should not decide halfway through a project.

Note  

Keep in mind that after you put Alien.as and both of the subclass class files into a package, you also have to modify each of the subclasses to make sure they are able to target Alien.as corrently. For example, with the Zapper subclass, you would need to write this:

  class Aliens.Zapper extends   Aliens.alien  

If we wanted to create a package for aliens, we could create a folder called Aliens. Inside, we would put the Alien.as, Zapper.as, and Shrinker.as files. So that the compiler knows where to look for other files, you must state the directory that the files are in as part of the class name. For Alien.as, the first line will change from this:

 class Alien extends MovieClip{ 

to this:

 class Aliens.Alien extends MovieClip{ 

You used the dotted naming structure to indicate the hierarchy just as with movie clips. You would need to repeat this process for both the other class files.

When it comes time to instantiate the classes, you would use code like the following:

In a class file, if you wanted to type a variable to the type Zapper , it would look like this:

 var myFirstAlien:Aliens.Zapper; 

The .fla has a command that will bring in a package so that you can use just the class name instead of the whole path . To do that, you use the import keyword. When you use import , it brings in the specified package, but it only saves the classes that you use into your .swf. If you wanted to import the Alien classes inside the Alien package, you would use the following:

 import Aliens.*; myZapper = new Zapper(); 

The asterisk is a wildcard that imports all classes within a package. In the example of the Alien package, this doesn't save much time. Packages can go as deep as you choose to make them, and a long path name can become tedious quickly. It is worth mentioning that import is not allowed in class files. Therefore, in those, you will still have to use the longhand notation.

Caution  

The shorthand names given by importing a package persist only for the current frame or event handler script. After that script is processed, you either need to use import again or use the longhand notation.




Macromedia Flash MX 2004 Game Programming
Macromedia Flash MX 2004 Game Programming (Premier Press Game Development)
ISBN: 1592000363
EAN: 2147483647
Year: 2004
Pages: 161

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