The Design Phase


After you've completed the analysis phase of an application, you have a map for what the application is supposed to do. However, that map is at such a high level that you cannot use it to begin writing code. The result of the analysis phase may be a map, but it doesn't tell you how you're going to get from point a to point b. For example, are you going to walk, drive, fly, or take the train? For that you need the next step, which we call the design phase.

In the design phase, you take the functional requirements documentation from the analysis phase and start to look at it from an architectural standpointlooking to identify subsystems and eventually classes. During the design phase you'll parse out the elements that should be written as classes. Then you determine the responsibilities for those classes as well as the relationships between the classes.

The goal of the design phase is to generate some sort of technical document that provides a blueprint of the application you intend to build, including all the specific subsystems and classes that you will use and the relationships between them. You should expect to use this technical document to help you break up the application development into individual tasks. You should also expect that the technical document clearly identifies dependencies and collaborations between classes.

As with the analysis phase, the design phase has no rule dictating what techniques and tools you must employ. There are many ways that different people approach the design phase, and we encourage you to find the one that works best for you. However, we have found that class responsibility and collaboration (CRC) cards are a technique that proves very helpful in the design phase. In the next section we'll discuss CRC cards in more detail.

Introducing CRC Cards

CRC cards are a low-tech, yet very effective, way to determine exactly what classes you need to write, what those classes need to be able to do, and how those classes relate.

Typically, you'll find that 3x5 or 4x6 lined index cards work best as CRC cards. At the top of the index card, write the name of the class. On the left side of the index card, list the responsibilities for the class. On the right side of the card, list the classes with which the class needs to collaborate to accomplish those responsibilities. Figure 1.2 illustrates the format for a CRC card.

Figure 1.2. The typical format for a CRC card.


CRC cards are useful because you can draw them up quickly and make changes just as quickly. Using CRC cards, you can rapidly map out the functionality of an application; when you decide to split a single class into two classes, combine two classes, or change a class name, you can do that with your CRC cards in a few seconds. You can also sit around a table with a team and work together on the cards.

Now that you know the format of CRC cards, you'll undoubtedly have a few questions regarding how to decide what constitutes a class, what responsibilities are, how to know what classes are collaborators, and so forth. The next few sections address each of these questions.

Determining Classes

Deciding what constitutes a class is as much an art as it is a science. Just as every painter has different ideas about composition, use of color, and so on, so too does every application designer have different ideas about how to build an application. However, you'll likely find certain guidelines helpful when you try to determine what classes your application needs.

It's often a good idea to look at your use cases to find classes. Classes are nouns. You can scan use cases for all the significant nouns and use those as classes in your application. For example, consider the Generate Map use case we described earlier in this chapter. From that use case we can easily identify these relevant nouns which are natural candidates for classes: "address form," "address data," "mapping service," "map data," and "map."

When you have selected all the candidates for classes, write them down on your CRC index cards. The next step is to determine the responsibilities for each class.

Determining Class Responsibilities

After you've decided on the initial candidates for classes, you can assign responsibilities to those classes. Assigning responsibilities is an important step because it helps you determine the viability of the class candidate. If a class candidate doesn't have any responsibilities, it must be unnecessary, and you can discard it. If the candidate seems to have too many responsibilities, it probably needs to be divided into two or more classes. There are some schools of thought that state that a class should have no more than one responsibility. While we respect that standpoint, we find it to be severe. A general rule of thumb that we use is that a class should have between one and three responsibilities.

It's important to understand what a responsibility is (and what it is not). A responsibility is essentially what a class (or an instance of the class) should be able to do or facilitate. Although there is a relationship between a class's methods and its responsibilities, they are not identical. You should not think of a class's responsibilities in terms of methods or method names. A class may require many methods to accomplish just one responsibility. At this point in the design, it's too early to map out the actual methods. Responsibilities are higher-level abstractions than methods.

A responsibility is usually something that can be written out in plain language in a few words. The following are examples of possible class responsibilities:

  • Create user input form

  • Validate user input

  • Encapsulate data model for a map

  • Handle requests and responses to and from server-side service

  • Draw vector map from data model

As you work on determining the responsibilities for the classes in your application, you will most likely drop classes, add classes, and change existing classes. These revisions are a desirable part of the process, which result in a well-considered design.

Although you can go through each class candidate and try to think of the responsibilities each class might have, that approach can be problematic. It encourages you to add responsibilities based on what you think the class candidate ought to do rather than based on what the application requires. A better approach is to scan the use cases for verbsboth explicit and implicit verbs. Explicit verbs are obvious because they are written in the use case steps. Implicit verbs are the verbs that are not written in the steps but are necessary for the successful completion of a step.

Determining Collaborators

Many, if not most, classes cannot fulfill all their responsibilities on their own. They must rely on other classes to assist them. The assisting classes are called collaborators. Collaborators generally lend a hand either by providing data or by enabling the class to offload functionality.

After you have defined classes and class responsibilities, the next step in the design phase is to determine what each class's collaborators are. This is extremely helpful in finding additional classes that you hadn't previously thought of. For example, consider a Map class whose responsibilities include drawing a vector map based on a data model. It might be immediately obvious that in such a case a MapData class would be a collaborator since Map would want to query MapData for the data needed to draw the map. Locating collaborators is useful for us in terms of determining relationships between existing classes. In this case, because we likely already have a CRC card for the MapData class derived from the "map data" noun we spotted in the use cases this collaborator did not help us find a new class. However, when we think about the Map class still more, we'll probably realize that drawing all the different types of elements on a map would probably be far too much for the Map class itself to handle. Instead we can rely on collaborators that draw the specific map elements, and we realize that these collaborators become new classes we missed before: Street, Highway, River, and CityMarker.

Elaborating on Relationships Between Classes

Classes have relationships with one another. When finding collaborating classes, you are finding the classes that have relationships. However, it's possible and necessary to determine what type of relationship these collaborating classes have. Although every relationship between classes will be unique, it is possible to generalize those relationships into the following categories:

  • Association

  • Aggregation

  • Inheritance

Association and aggregation are types of relationships that can more generally be called composition. Later in this chapter (in the section titled, "Inheritance and Composition"), we'll compare and contrast the generalized principals of composition and inheritance as they apply to implementation.

The Association Relationship

Association is the weakest of these relationships. Association relationships are also sometimes called dependency relationships. When two classes are related in this way, one of the classes relies on its collaborator to help with one or more of its responsibilities.

An example of an association relationship is the relationship between a Map and a MapData class. The Map class has a dependency on the MapData class. Without a MapData instance, a Map object wouldn't be able to draw the map.

Associations are perhaps the most common sort of relationship between classes. You can think of associations as "uses" relationships, meaning that Map "uses" MapData.

The Aggregation Relationship

Aggregation is a stronger form of composition relationship than the association relationship. When classes are related by aggregation, the life cycles of the classes are linked. When classes are related by association, one class instance can be created or destroyed without necessarily affecting the other. However, when classes are related by aggregation, it implies that one class is the owner of the collaborator class. If the owner class is destroyed, so too are the aggregate collaborator classes.

An example of an aggregation relationship is that of the Map and Street classes. You can think of aggregations as "has a" relationships, meaning that Map "has a" Street. That doesn't mean that all Street objects are owned by Map objects. But this relationship does state that Map objects can have Street objects, and when the Map object is destroyed, so too are the Street objects it owns.

The Inheritance Relationship

Inheritance is the strongest sort of relationship between classes. When a class inherits from an existing class, it initially looks exactly like the class from which it inherits. The entire interface and implementation (more on these topics in the next chapter) of the existing class (what we call the superclass or base class) are passed down to the new class (what we call the subclass.) The relationship is so strong between superclasses and subclasses that subclass instances can even stand in for superclass instances in many cases. Because of the strength of inheritance relationships we say that inheritance defines an "is a" relationship such that the subclass "is a" superclass.

Inheritance relationships allow you to create abstractions that are shared by many similar classes. For example, Street, Highway, River, and CityMarker are all types of map elements. If all the classes share common interfaces and implementations, these classes might have a lot of duplicate and redundant code. You can abstract that code by placing it into a new MapElement class. Street, Highway, River, and CityMarker can then all inherit from the MapElement class. They will automatically inherit the interface and implementation from MapElement, which will remove the need to repeat that code in each of the subclasses. It also means that you can begin to use polymorphism. Although we'll talk about this topic in more detail in the next chapter, the idea behind polymorphism is that a more specific type can substitute for a more general type. In other words, the Map class can have an aggregation relationship with MapElement rather than having aggregation relationships with Street, Highway, River, and CityMarker. That distinction is very important because if you later wanted to add a Bridge class, you could simply define it such that it inherits from MapElement, and the Map object would automatically work with Bridge objects without your having to rewrite any of the Map code.

Although inheritance relationships are very powerful, they also tend to create very rigid relationships. Inheritance has its place and deserves credit for all that it can do. However, so much emphasis has been placed on inheritance relationships in many programming communities that it is often overused and misused. Inheritance relationships should generally be the least frequent type of relationships in your applications. Inheritance enables polymorphism, which is extremely valuable. However, inheritance is not the only way to enable polymorphism, as you'll read in the next chapter. We'll compare and contrast inheritance with composition relationships in the "Inheritance and Composition" section later in this chapter.

Formalizing Public APIs

By this point, you've decided on the classes your application requires as well as the responsibilities of each class, the class collaborators, and the relationships each class has with those collaborators. Although you might be anxious to start coding right now, there are still some steps to complete in the design phase.

The next step is to formalize the public APIs (Application Programming Interface, which means the public methods) of the classes.

Formalizing the API for a class is a matter of translating the responsibilities into method signatures. Not all responsibilities necessarily translate into public methods because some of what a class is responsible for might be private. For example, the AddressForm class might have a responsibility to validate user input. That is probably not something that translates into a public method. Rather, it is far more likely that this responsibility is handled internally by the class when the user clicks a button. However, some class responsibilities might translate into several public methods. For example, in the case of our map example, the responsibility "handle request and responses to and from server-side service" might translate into the following methods (depending on the application requirements):

function getMapDataForAddress(address:AddressData):void; function getSavedMapData(id:uint):void;


Note

In the preceding example, the two methods are purely based on speculation as to what sorts of methods such an application might require for a server-side service proxy (often called a remote proxy). Furthermore, both methods are declared with void return types because the assumption is that the class is a proxy to a server-side service that works asynchronously with Flash Player, and responses will be handled by event listeners.


Using UML for Design

We first mentioned UML in relation to analysis. However, one of the most common uses of UML is during the design phase because you can use UML class diagrams to visually represent all the classes, their APIs, and the relationships between the classes. UML class diagrams are really useful because they allow you to look at all the classes and there relationships all at one time in a relatively succinct format. Usually a UML class diagram doesn't replace the need for technical documentation. However, UML class diagrams can often supplement technical documentation and serve as a useful tool both during the design phase as well as during the implementation phase when you must actually write all the classes shown in a UML class diagram. Figure 1.3 shows a very simple UML class diagram that shows two classes and an interface.

Figure 1.3. A simple UML class diagram.


Note that this figure shows only public class members, yet you can also represent private and protected members.

Not only does UML provide a nice way to visualize the classes used by an application, but it also provides the possibility to export stub code for all the necessary classes and interfaces. At the time of this writing there is no known ActionScript 3.0 stub code generator for UML. However, since this is a common feature for many other languages (Java, C#, etc.) it is reasonable to think that there will be an ActionScript 3.0 generator for UML in the near future.




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