The Implementation Phase


Following the design phase is the implementation phase. In the implementation phase, you actually write the code you have planned out. If you've had successful analysis and design phases, the implementation of your application should be relatively straightforwardsimply a matter of coloring in the lines, so to speak. By the time you get to the implementation phase, you should already have decided on the classes, their relationships, their responsibilities, and their APIs.

Much of the implementation phase simply involves writing ActionScript code, and as the one step you can't skip, it is the phase with which everyone is familiar. As such, we're not going to focus on the details of how to write classes. However, there are several topics that bear further discussion, namely:

  • Coding conventions

  • Encapsulation

  • Composition and inheritance

  • Coupling

Coding Conventions

There are few rules for naming classes, packages, variables, functions, and interfaces in ActionScript. In each case, you can use only letters, numbers, dollar signs ($), and underscores (_) and the first character must not be a number. Although the rules are few, there are still conventions for naming that you might find useful. At the very least, you will find it useful to know what conventions we use in this book. You should know that the conventions we use aren't the only conventions, and you aren't obligated to use them. We introduce this topic here because consistent and conscious coding conventions are a boon to application development. By applying conventions consistently you can expect to write code that is easily read by you and anyone else during team development. Remember that classes can involve hundreds of lines of code, and using consistent conventions helps you to more quickly identify parts of the code and their purposes.

Variables and Functions

For variables, it is a convention to use initial lowercase letters. Consider this example:

var city:Map;


Generally, it is advisable to use as the name words and phrases that describe the variable. For example, city is probably a much better name for a Map variable than m would be. Often times, it's possible to more accurately describe a variable using several words. In such cases, the convention is to use a style called camel case (sometimes called inter caps) in which the first letter of each word (except the first) is capitalized, as in this example:

var cityMap:Map;


Class properties are special sorts of variables, and as such they use the same naming convention as variables. However, to better distinguish between local variables and class properties, it is a convention to name all private properties with an initial underscore, as in this example:

private var _cityMap:Map;


Note

The issue of underscores for private properties is a contentious one among developers. It is our preference to use underscores as we feel they help clearly differentiate between private properties and local variables. However, some developers will argue vehemently against the use of underscores as they feel there is no significant benefit in their use.


Functions (and methods) also follow the same naming conventions as variables. Function names should start with lowercase letters and use camel case formatting when the function name consists of more than one word. Consider this example:

public function getMapDataForAddress(address:AddressData):void;


Parameters are also special variables, and as such they use the same naming conventions as variables, as you can see in the preceding example.

Unlike private properties it is not common to use underscores for private methods. The logic behind this is that a method is not generally defined within another method as a local variable might be defined within a method. Therefore, it's always clear that a method is a method without having to use underscores.

Note

The variable and function/method naming conventions presented here are not intended to be comprehensive of all possible naming conventions. Many developers like to use additional conventions such as using variable prefixes to denote type. We are presenting the conventions that we find useful and that we use in this book. You are always welcome to use whatever conventions you find helpful.


Constants

Constants are special types of fields; you can define them with a value, but you cannot change the value subsequently. You've likely seen many constants in the Flash Player events API such as EVENT.COMPLETE and MOUSEEVENT.CLICK. As you can see, constants use all uppercase characters by convention. If a constant name uses more than one word, the words are delimited by an underscore, as in MouseEvent.MOUSE_MOVE.

Note

Constants are a new feature in ActionScript 3.0.


Classes and Interfaces

By convention, class names always start with an uppercase character. Class names also use camel case when necessary. In addition, class names should always be nouns.

Interfaces use the same naming conventions as classes except that they have one additional convention: Interface names always start with the letter I (meaning interface.) Additionally, interfaces do not always have to use nouns as names. Although it's not uncommon to name an interface with a noun (e.g. ICollection) it's equally common to use an adjective ending in -able. For example, the Flash Player API includes the following ActionScript 3.0 interfaces: IExternalizable and IBitmapDrawable.

Packages

For the most part, package names follow the same conventions as variables: They start with lowercase letters. There are two schools of thought regarding the use of camel case in package names. One group uses camel case while the other group uses exclusively lowercase characters in package names. In this book we do not employ camel case in package names.

There's yet another important convention when it comes to package names. One of the functions of packages is to ensure that classes exist within unique namespaces. For example, two classes called Example cannot be created in the same package, but may exist in two separate packages. When you decide on package names, try to ensure that the package name guarantees uniqueness. That way, if you happen to use your Example class in a project with an Example class from an existing library, the two classes can coexist.

By convention, package names can guarantee uniqueness by using subpackages in order of descending order of specificity. When a class is part of a library belonging to a company or organization, the convention is to name the packages starting with the organization's domain name in reverse order. The first part of most package names is the top-level domain such as com or org. The second part of most package names is the domain such as google or amazon. If the classes are specific to a project, the project name follows the company's domain name. The classes themselves are generally placed in subpackages that group them by classification. For example, utility classes might go in a utils subpackage and service proxy classes might go in a services package. As an example, imagine that you're writing a class called LoggingService that is specific to a project with a code name of JediKnight for your company called ExampleCompany (with a domain name of examplecompany.com.) You might place that class in the following package:

com.examplecompany.jediknight.services


Encapsulation

One of the rules of good object-oriented design is that all classes should be black boxes: you can put things in and take things out, but you can't determine how it operates. In other words, the only way to interact with a class instance is to use its public methods. You should never be able to look into an object or change the object's state except by asking the object to tell you about itself or to change its own state. The object must always maintain sovereignty. The minute an object is no longer in charge of its own internal world, the entire object-oriented universe starts to crumble and fall apart into an unmanageable train wreck.

This idea of classes being black boxes is a fundamental principle of object-oriented design called encapsulation. Encapsulation is absolutely necessary for an object-oriented design to succeed because it enables objects to interact with one another in known and well-defined ways. This approach models the world in which we live in many ways. Every object in the physical world has boundaries that define it and its interface with the world around it. Your body interacts with the air by way of respiration, for example. Without these well-defined interfaces there would be chaos, and it would be impossible to interact with anything in a useful or meaningful way.

Implementing classes so that they adhere to the principle of encapsulation is quite simple. To achieve this goal, there are just two basic rules:

  1. Don't use any public properties.

  2. Don't reference objects outside the class unless the reference was passed to the class as a parameter.

Public Properties

Properties store an object's state. As we've already said, an object must be in control of its own state. Public properties allow other objects to directly change an object's state without the object being in control. The implications of this can be far-reaching, but we can see the problem with a simple example. Consider a Student class that models a student at a school. One of the fields that comprise a Student object's state is the GPA (grade point average). It might seem like a good idea to simply define the class with a public gpa property. However, consider that GPAs are generally constrained to a specific range of values (0 to 4, for example). With a public property, there's no way for the application to guarantee that a student's GPA will always be in the valid range. If the property is public, you can simply set the value to any numeric value regardless of whether or not it is within the valid range, as this example does:

student.gpa = 400;


As if that wasn't bad enough, there are further ramifications. What if there are other collaborating objects that must be updated with a student's GPA changes? For example, a SchoolRecord object might need to know when a GPA changes in general, and a Parent object might need to know when the GPA drops below or raises above a certain level. If the Student object doesn't even know when its own state changes, it can not very well notify other objects when its state changes.

The solution to public properties is to use private properties with accessor methods. In ActionScript, we call the accessor methods getter and setter methods, and ActionScript enables two types of getters and setters: explicit and implicit. An explicit getter or setter is a normal method, typically using the word get or set in the name of the method. For example, rather than declaring a public gpa property, you can declare a private _gpa property and then use methods called getGPA() and setGPA(). Consider this example:

public function getGPA():Number {    return _gpa; } public function setGPA(value:Number):void {    if(value > 4) {       _gpa = 4;    }    else if(value < 0) {       _gpa = 0;    }    else {       _gpa = value;    }    dispatchEvent(new Event(Event.CHANGE)); }


Notice that the setter method uses boundary testing to verify that the value is always in the valid range between 0 and 4. This example simply corrects values outside the valid range, but another implementation might throw an error. The method also dispatches an event that can notify listeners (such as a SchoolRecord or Parent object). When you want to set the GPA for a student, you can simply call the setGPA() method and pass it the value, as shown here:

student.setGPA(4);


When you want to retrieve the value you can call getGPA(), as in this example:

textfield.text = "GPA: " + student.getGPA();


Implicit getters and setters are similar to explicit getters and setters. In fact, the implementation of implicit methods can look almost identical to that for explicit getters and setters. The difference is that implicit getters and setters are defined as methods, but they look like properties when used. The syntax for implicit getters and setters uses the keywords get and set after the function keyword. The following example rewrites the preceding explicit methods as implicit methods:

public function get gpa():Number {    return _gpa; } public function set gpa(value:Number):void {    if(value > 4) {       _gpa = 4;    }    else if(value < 0) {       _gpa = 0;    }    else {       _gpa = value;    }    dispatchEvent(new Event(Event.CHANGE)); }


When you want to call the implicit setter method, you use it as part of an assignment statement. The value you assign to the "property" is passed to the setter method, like this:

student.gpa = 4;


You can call the getter method when you reference the "property" in a context that attempts to read the value, as shown here:

textfield.text = "GPA: " + student.gpa;


External References

A class should never directly reference any object that is outside of itself unless it obtains that reference through its public interface. A class can declare private properties and local variables and can reference those objects internally because they exist within the class. A class can also reference an outside object if the reference was passed into it via a public method. For example, a Student class might define a method called attendClass() that accepts an AcademicClass parameter. The Student object can then reference that object because it was passed in as part of a method call.

public class Student {    public function _classes:Array;                                                                                         public function Student() {       _classes = new Array();    }    public function attendClass(class:AcademicClass):void {       _classes.push(class);       // Now that the class was passed in as a parameter the        // Student instance can store that reference in the array        // and use it later. This doesn't break encapsulation        // because the reference was passed in via the public API.    }    // Remainder of implmentation. }


Designing for Encapsulation

Encapsulation is an extremely important principle, and it can have far-reaching consequences. Consider a School class that has a private property called _students, an array of all the students who attend the school. If you need to make the students available to collaborators with the School object (for example, a SchoolDistrict class might need to know about all the students at all the schools in the district), you can make the array accessible using a getter method, as shown here:

public function get students():Array {    return _students; }


Even though you aren't using a public property, the design in this example breaks the principle of encapsulation. Consider what happens when you retrieve the _students array and make changes to it directly:

school.students.splice(10, 5);


The preceding code removes five students from a school, but the school never receives notification about the removal of the students. That is obviously not the behavior you would want (a school should always know when students have been removed). You can address this issue in several ways. One way is to simply return a copy rather than a reference, as shown here:

public function get students():Array {     return _students.concat(); }


Another solution is to employ the Iterator pattern (described in Chapter 7, "Iterator Pattern"). Regardless of which solution you use, you are solving the design flaw that broke the principle of encapsulation.

Most design patterns are solutions to problems relating to encapsulation. In many cases, encapsulation might appear to be in direct opposition to other important design principles. For example, many applications need to have globally accessible objects of specific types. An application might need a globally accessible User object that represents the current user of the application. As we've already discussed, it would break encapsulation if all the other classes in the application had hard-coded references to that one specific User object. However, using the Singleton pattern (described in Chapter 4), you can achieve the goal of a globally accessible object without having to directly reference a specific object.

Inheritance and Composition

One class can leverage the functionality of another class in one of two basic ways: inheritance or composition. Both are powerful techniques. Inheritance allows you to define a new class so that it automatically gets the interface and implementation of an existing class. The following code declares a class called Employee:

public class Employee {    public function Employee() {}    public function work():void {       trace("working");    } }


The new class, which we call the subclass, can build on the foundation of the existing class, which we call the superclass or base class, without needing to rewrite the original code or write any new code to use the superclass code. There are different types of employees, and we can define different subtypes by inheriting from the Employee superclass. For example, the following Executive class inherits from Employee by using the extends keyword:

public class Executive extends Employee {    public function Executive() {}    public function attendMeeting():void {       trace("attending meeting");    } }


Furthermore, inheritance automatically enables polymorphism because the subclass inherits the interface of the superclass. That means that an Executive object is also an Employee…just a more specific type. An Executive object can be used any time an Employee object is expected although the reverse is not true:an Employee object cannot stand in for an Executive object. Note that the Executive class defines another method called attendMeeting(). Because Executive objects inherit from the Employee superclass, you can call the work() method for an Executive and you can also call the attendMeeting() method which is specific to Executive.

In contrast with inheritance, composition allows you to write a new class (a front-end class) that has an instance of an existing class (the back-end class). Every time you define a class with a property whose type is another class, you are using composition in some sense. The following example is a rewrite of the Executive class example just shown so that it uses composition rather than inheritance:

public class Executive {    private var _employee:Employee;    public function Executive() {}    public function attendMeeting():void {       trace("attend meeting");    }    public function work():void {       _employee.work();    } }


When you use composition, the new (front-end) class does not automatically inherit the interface of the existing (back-end) class. The front-end class can use the back-end class instance only by way of its public interface. If the front-end class needs to have part or all of the same interface as the back-end class, you must write code that defines the interface as well as its implementation. That is the reason that this rewrite of the Executive class has to define a work() method. Unlike the example that used inheritance, the composition version of the Executive class does not inherit the work() method. If you want the work() method to be part of the Executive interface, you must define it. The preceding example uses a technique called delegation to pass along the method call to the composed object.

Because a class that composes an instance of another class does not automatically inherit the object's interface, composition does not automatically enable polymorphism. In other words, using composition, an Executive object is not an Employee, and it cannot stand in for an Employee. (The solution to this issue is to use interface constructs as discussed earlier in this chapter.)

In reading the preceding paragraphs, you might think that inheritance sounds like a much better technique for reusing existing functionality. It sounds like composition requires much more work with little or no advantage. Yet both inheritance and composition have their advantages and disadvantages.

Advantages and Disadvantages of Inheritance

As you've seen already, inheritance has the following advantages:

  • Simplicity of use: Inheritance is a concept built into the language. All you have to do is use the extends keyword in order to define one class so that it inherits both the interface and the implementation of an existing class.

  • Ability to change inherited implementation: By using the overrides keyword, you can change the implementation inherited for a particular method.

Yet inheritance also has its disadvantages:

  • Implementations are fixed at compile-time: For example, if a Chart3D class inherits from the BarChart class, then it's impossible at runtime to apply the 3D functionality to a LineGraph object.

  • Supports weak encapsulation and fragile structures: Subclasses have privileged access to a superclass's implementation. Anything that is marked as public, internal, or protected is accessible to a subclass. This means that encapsulation is weak in inheritance relationships. Because of this, it's possible that a change to a superclass implementation could break subclasses even if the public interface does not change.

  • Superclass interface changes necessarily change subclasses: If you change the signature of a superclass method the change will ripple to all subclasses.

  • ActionScript allows a class to inherit directly from just one class (as opposed to multiple inheritance, a concept utilized by very few languages): Suppose that all Executive objects share the functionality of both Employee and DecisionMaker classes. ActionScript allows Executive to inherit from just one of those classes, not both.

Advantages and Disadvantages of Composition

Although we haven't yet mentioned the advantages of composition, they are numerous. Some of the most prominent advantages are as follows:

  • Implementations are configurable at runtime: For example, if a Chart3D class operates on an object typed as Chart (of which there are many subtypes such as BarChart and LineGraph), the Chart3D class can operate on any of those subtypes. The specific subtype can be set at runtime.

  • Supports good encapsulation and adaptable structures: Classes that use composition are forced to go through the back-end class public interfaces. That means that they enforce good encapsulation. That also means that changes in implementation of the back-end classes are less likely to break classes that use them. As long as the interface remains the same, the front-end classes won't break.

  • Interface changes have limited ripple effect: When the interface of a back-end class changes, it will break front-end classes that rely on the old version of the interface. However, the damage is contained and generally fairly trivial to correct. Because interfaces are not inherited when using composition, the changes affect only the front-end class, but not classes that in turn compose instances of the front-end class. In other words, if Executive is a front-end class for Employee and the interface for Employee changes, you will most likely have to make changes to Executive. However, the interface for Executive does not change. That means that if a Company class composes an Executive object, the Company class does not have to change.

  • Composition allows a front-end class to have relationships with many back-end classes: Using composition, an Executive class can have both an Employee and a DecisionMaker property.

Yet composition is not without its disadvantages:

  • Frequently requires more code than inheritance: If a front-end class needs to use some or all of a back-end class's interface, it must re-create it.

  • Often more difficult to read than inheritance: Inheritance establishes a very straightforward relationship. Composition is often less direct and presents a trail that's more difficult to follow if you're not familiar with the code.

Which to Use: Inheritance or Composition

Generally, the rule of thumb is to favor object composition over inheritance. The advantages of object composition outnumber the disadvantages. Furthermore, the disadvantages of composition are not obstacles as much as they are simply inconveniences. Because inheritance is so much more straightforward, it's a lot easier to teach and learn in many cases, and it tends to be overemphasized and overused by many people in the ActionScript development community. For this reason, it's often beneficial for ActionScript developers to determine whether composition is the best option for establishing a relationship between classes.

With that said, it's also worth noting that with the surge of interest in object-oriented design and design patterns in the ActionScript community, inheritance has been maligned in many circles. It's important to understand several things about this conflict:

  • Inheritance is not wrong: Just because you should favor composition does not mean that inheritance is never appropriate. Inheritance is a better solution in some cases. It's difficult to make rules that tell you when to use inheritance and when to use composition. However, as a general guideline, it's advisable to use inheritance in the following situations: When a new class really does define a subtype of an existing class, when the new class is not likely to have subclasses itself (limiting inheritance chains keeps some of the disadvantages of inheritance at bay), when the new class would benefit greatly by inheriting part of the existing class's implementation that is hidden from the public, and when the new class does not have special requirements (for example, it needs to be adaptable to significant changes at runtime).

  • Inheritance and composition are not competitors: Although it is true that in almost all cases two classes will be related by either inheritance or composition (and not both), that does not mean that these two types of relationships can not work together. In fact, most classes that use inheritance also use composition.

Conventional teaching says that to determine whether two classes should be related by inheritance or composition, you should use the "is a/has a" test. The "is a/has a" test says that you should answer the following question: Is (new class) a (existing class) or does (new class) have a (existing class)? If the new class is a more specific version of the existing class, the relationship is inheritance. If the new class simply has an instance of the existing class as a property, the relationship is composition. Although that guideline can be useful, it is not definitive. Consider an example using an existing class called Student and a new class called School. If we ask whether School is a Student, the answer is obvious: a School is not a Student. Therefore, the relationship must be composition, not inheritance. Yet just because we can answer that a new class is a more specific version of an existing class doesn't mean that the relationship should necessarily be inheritance. For example, consider the relationship between a HighSchool class and a School class. If you use only the "is a/has a" test, you might determine that a HighSchool is a School and therefore the relationship is inheritance. Yet consider what happens if you need to have a HighSchool object that uses experimental administration structure and teaching techniques. We can assume that the implementation for School deals with traditional school systems and infrastructure and would not meet the needs of an experimental school. An inheritance relationship between School and HighSchool is rigid. If you use composition to define the relationship, it's possible to create an experimental high school type at compile type by substituting an ExperimentalSchool instance for the School property of a HighSchool object.

Coupling

Coupling refers to the degree to which two objects must know about one another. When the objects have to know a great deal about one another to work, we call that tight coupling; when they have to know little to nothing about one another, we call that loose coupling. In object-oriented design, we generally strive to have loose coupling among the objects in the system. Loose coupling creates flexible and adaptable systems. If objects are tightly coupled, the system is rigidone change in one object can cascade and break the entire system. If objects are loosely coupled, changes are much less likely to break things, and even when changes do cause malfunctions, the malfunctions are generally contained.

Many design patterns aim to create loosely coupled systems. For example, if an object needs to ask another object to run a behavior, the traditional way to accomplish this goal is for the object to have a reference to the collaborator and to call a method of that collaborator. That way of structuring an application uses tight coupling because the calling object has to have a reference to the collaborator and it has to know the signature of the method it wants to call. It's difficult to make changes to that structure. The Command pattern described in Chapter 10 addresses this issue by completely decoupling the objects. The Command pattern adds an intermediary layer that parameterizes the behavior and allows the calling object to simply have a reference to the intermediary object and know about a standard interface. This is just one example of how design patterns can promote loose coupling or decoupling, and you'll see many more examples throughout the book as you read about each of the patterns.




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