TOC PREV NEXT INDEX

Put your logo here!

The Essence of Object Oriented Programming with Java and UML


Chapter 4

Object-Oriented Analysis and Design

 

OK, so now you are starting to think in objects. You know what an object is, how objects can be used together in different kinds of organizations, and how you can build objects using Java. Now you are ready to write some great software.

Good software doesn't just happen. Even the world's best programmers can't just sit down in front of a computer and write great software. Developing great software involves knowing what the software is supposed to do, having a plan to build that software, and using good practices to produce a high quality working software system. Most software projects involve many people and are developed over a period of time that can be as long as several years. Even simple one-person projects should be developed with some discipline. The process of building an entire software system is often called Object-Oriented Analysis and Design (OOAD). In this chapter, we will cover the essential parts of OOAD.

The process of building a software system has many things in common with building a house or building. For a very small building, the process can be pretty informal. But if you need to build a large office building, the process is much longer and complex. The same is true for a software system.

The first step, of course, is knowing what you want to build. There is typically someone who wants a building (customer), and someone who is going to design and build it (designer). Sometimes, but usually only for small projects, those two are the same person. But there is still that first step - we need a building, it will be this size, more or less, and will serve some purpose - a storage shed, a home, a warehouse.

Once the decision has been made to build a building (or software system), the next step is to develop some specifications. Even if you are building a simple shed as a weekend project, you have to decide where it will go, how big it will be, what material you will use to build it. It is even possible to decide to buy a predesigned kit. This analogy holds even for a small software system. You must start with at least some analysis of what the software should do, what is basic features are, where you will use the software, and even if there is an existing system that will serve your needs. As with a building a small building, building a small software system doesn't necessarily require a whole lot of people, or super experts to design a successful system.

Now, consider building a house. The process is more complicated. The customer usually first starts with an architect or experienced designer. After a month or more of interaction between the designer and the customer, the result is a set of fairly detailed plans of how big the house will be, how the rooms will be laid out, and perhaps what basic materials will be used. After this design is settled on, the details need to be filled in. What siding will be used? What color will the rooms be? What lighting and plumbing fixtures will be used? Finally, the home will actually be built, usually with a crew of less than ten workers, over a few months of construction. The tools required to build a house are usually basic and not necessarily expensive. Often the designer will supervise, or even participate in the construction of the home.

A small to medium sized software project isn't a whole lot different in the approach required. There will be a period of planning between the customer and the software designer. The customer will be the one who specifies what is most important to include in the software system. The designer will analyze the requirements, and produce a high level design for the system - what it needs to do, how it will be organized, what its parts are. After the customer and designer agree, then a more detailed design is produced to determine such details as what programming language will be used, what the objects in the system are, and what the basic structures of those objects are. Finally, programmers will turn the design into a running program. The time frames for this size of project typically run about a year or so. The number of programmers varies - usually at least 2 or 3, but less than 10. And depending on the specific application, there usually isn't a need for developers with specialized knowledge. For projects of this size, often basic software tools will be enough, with perhaps a few specialized tools being used.

Finally, consider building a huge office building. Now the situation is much different. The customer will likely have put a considerable effort into deciding that they need to build a big building, and just what that building will do for their business. They need to then work with a large architectural firm with considerable experience in building large office buildings. They would use different firms if the project were for a highway, airport, or power plant. The analysis and design phase of the project can take many months, even years, before the actual construction begins. The construction company is likely to be completely independent of the design company, and will use tens or hundreds of workers skilled in building office buildings. They will work with a detailed set of plans and specifications produced by the designers. The project will likely need large, expensive equipment that requires specialists to operate. The whole project may require layers of management to keep everyone involved on track.

A large software project is in many ways similar. The up front analysis and design phase can take years. The design process will likely require the input from experts in various fields of the application area, and experts in designing large software systems of a specific nature. The result of the analysis and design will be a detailed specification that can be passed on to software companies that specialize in building large systems. The whole process may take years and involve tens or hundreds of software designers and programmers. Expensive software tools that require extensive training to use may be needed to make the project work, and outside consultants may be required.

There are some other traits buildings and software have in common. First, the result of the effort will be around for a long time. You may think you are writing a quick and dirty software application, but inevitably, it will continue to exist for years and years. Second, after you are finished, there will be bugs. You will have missed getting a nail in the right spot, and your shed roof leaks. Your software will have bugs. And finally, you will end up wanting to add on - either to your building, or to your software. And you will occasionally need to refurbish to keep up with the latest trends in house interiors, or user interface design.

Of course, this analogy isn't perfect. People have been building structures for a long time. There are standard construction materials and techniques. There are official building codes, and inspectors to be sure the codes are followed. Software isn't quite that mature, yet, and in many ways, software can be much more complex. The number of lines of code in a software project can easily exceed the number of parts used in a building. And software doesn't have the equivalent of building codes and building inspectors (and is isn't clear that it ever can, although people are trying). There is another difference. For many software projects, good programmers are likely to participate in all phases of building the system, from start to finish.

Even so, there are some important things to learn from this analogy. First, even the smallest projects really need some level of analysis and design. Even so, smaller projects can be done with fewer people and less formal techniques. As the complexity of a project increases, the more it will benefit from more formal analysis and design methodologies.

So, how do you develop a real software system? Where do you start? What do you do next? In the rest of this chapter, we will go over some basic software analysis and design techniques that can be used at some level in almost any software system.

Software Methodologies

Over the years, many of the best software experts have discovered and designed object-oriented design techniques that raise software development above the level of black magic and artistry to something closer to using standard engineering practices. Not all software projects are the same. Some are very big, some small, some involve well-understood problems, and some are risky and explore uncharted territory, and different design techniques will apply to different software projects.

A development methodology is set of practices or guidelines used to develop software. Over the years, there have been many different, often competing, software development methodologies. The most current methodologies are designed to work especially well with object-oriented development. Because not all software projects are the same, there are currently several different software development methodologies that can be applied to different kinds of software projects. Some methodologies, such as the Rational Unified Process, seem best used for very large, multi-year projects with large teams of programmers. Other methodologies, such as Extreme Programming (XP), work better with smaller scale projects with ten or fewer programmers.

Methodologies developed before the widespread use of object orientation are now known as structured methodologies. While object orientation has proven itself to be much more productive than older structured development and is the dominant development paradigm today, there are still many active software projects that use non-object-oriented languages and structured methodologies. We will focus exclusively on OO methodologies in this book.

Note that the UML is not a development methodology, but a notation. While the design of the UML was influenced heavily by the designers of the Rational Unified Process methodology, it can be used by almost any of the other methodologies, although not all elements of the UML are necessarily used by a specific methodology.

In this chapter, we will try to cover analysis and design techniques that can be applied to any development technology. We will discuss the specifics of some of current methodologies in more detail in Chapter 9.

Even though the different development methodologies vary widely in their detail, there is some commonality. When examined at the fundamental level, almost all of them include in some way or another the following three basic parts:

Different methodologies or other summaries of OOAD may use different vocabulary and names, have different steps, or more steps; but you can usually map other viewpoints back to these three steps. A slightly expanded view of these steps includes some of the terms often used by other methodologies:

Before the wide spread use of object-oriented programming, older development methodologies tended to treat these steps as happening in a strictly sequential order, often known as waterfall development1. Each step of the development cycle flowed downstream into the next. Object-oriented methodologies tend to treat development as a much more iterative process: Plan, Build, Release a Version, and then repeat with refinements. The exact steps used by each methodology vary, but the software system is typically implemented in series of development iterations. Each iteration usually results in a release of the system with partial functionality. After each iteration, the process is repeated, with lessons learned in previous iterations applied to the next to improve the process. The different object-oriented methodologies vary most in the details of the overall process, and in the approaches they take with each of the plan - build - release steps.

One thing that almost all modern methodologies have in common is their ability to use the UML. The fact that different parts of the UML can be used for various phases of software design by almost any methodology accounts for its widespread acceptance.

The Elements of a Software Project

As we noted earlier, software projects come in all sizes. While a high portion of current software development is maintenance and enhancement of existing systems, there are still new software projects, big and small, being started all the time. Just how does a new project get started?

A software project will get started when some individual or organization, most often after some considerable contemplation, decides that a new software system is needed by the organization. At this point, the organization, or customer, will contact a software developer to discuss creating such a system. The customer and developer may be from the same company, but software development is also commonly done by external organizations.

The customer will meet with the developer, and provide a description of the software system. This description will be from the perspective of the customer and the problem domain of the system. Together, the customer and developer will produce a more detailed initial specification, which can then be used by the developer to determine the feasibility and cost estimation for the proposed project.

customer The organization that needs a software system, and is paying for the development. The customer should have a clear idea of what the software system needs to do, and how it can best help the customer's organization.

developer An organization that develops software for a customer. The developer will work with the customer to design a software system that best meets the needs of the customer given the time and financial constraints imposed by the customer.

problem domain The field or area a software system is being developed for. An accounting system would fall into a financial problem domain, and require input from financial experts to its design, for example.

initial specification An early description of what a software system needs to do. Depending on the overall size of the project, the initial specification can be simple, or consist of extensive documentation.

feasibility Given an initial specification, the developer will work with the customer to decide if it is feasible to continue with the development of a software project given the technical, time, and financial constraints. This is also known as risk assessment - is it within acceptable risks to proceed with the project?

estimation Before a project can proceed, the customer usually needs a feasibility study and an estimation of the final cost of the system.

These steps are the first part of the whole process. The amount of effort required for this initial planning will depend on the size of the project. Just as building a shed might require a simple sketch and a call to the lumberyard to get an idea of what the materials would cost, a simple software project might involve a meeting or two between the customer and the developer to get an idea of the feasibility and costs of the project. As the complexity goes up, the need for more careful initial planning goes up.

Once a project gets at least an initial green light, the planning process moves into a more detailed analysis phase. The scale of the analysis phase again depends on the scale of the project. For large projects, the initial planning will usually not involve any real coding, although some small prototypes or coding experiments might be required for the feasibility study.

There are typically at least two parts to any planning phase. The first part is analysis of the current state of the system. Analysis involves the software developers, the project managers, and the customers of the system. The second part of planning is called design. The results of the analysis planning are used by the development team to produce a software design that can be used for the Build phase.

In older structured methodologies, the analysis and design phases were distinct operations. With object-oriented systems, the difference between analysis and design is somewhat less distinct. The most significant difference is likely to be the participants and the level of detail involved in the process.

In OO, a major goal of both the initial analysis and design is to discover the objects that the system will require, what the responsibilities of the objects are, and how they will interact with each other. In analysis, objects are treated at a higher level than in design. The inner details of the objects are ignored, as are the exact interactions and implementations of objects. These details are worked out during design.

The planning process is repeated in subsequent iterations of the development cycle. The results of the previous build and release are used to refine the specifications of the system. This will include refining the features of the system, as well as the objects used by the system.

The over all size of the project will determine just how distinct the different parts of the planning, building and release phases of a project are. For some of the newer agile development methodologies, such as XP, suitable for small to medium sized projects, the three phases can become quite fuzzy. The larger the project, the more likely it is that a heavyweight methodology will be required, and that the distinction between phases will be more apparent.

UML - Use Cases

Use cases are used to understand various interactions between different parts of a system. Each use case is made up of scenarios, which are a sequence of steps describing an interaction between a user and the system. The users are called actors.

Consider our example of a reader borrowing a book from a library. In such a case, the Reader will usually interact with a Librarian, who will carry out the transaction. In a library system, the simplest case, or scenario, would consist of the following steps:

1. The Reader finds a Book to Borrow, hands to Librarian.

2. The Librarian identifies the Reader in Library system.

3. The Librarian identifies the Book in Library system.

4. The Librarian lends the Book to the Reader with Library system.

5. The borrowing is registered in Library system.

This is just one scenario, and there will be others. The Reader might not find the desired book. The book might not be allowed to leave the Library. The Reader might have overdue books, or not be in the system yet. Each of these possibilities should be examined in alternative scenarios. Together, the scenarios make up one use case.

The goal of this is to identify actors, objects, and interactions between them. All this can be a useful part of the analysis, and aid in understanding the system. The actors don't need to be people - they can be other objects in the system, for example.

This simple borrowing scenario serves as the basis for a UML use case diagram. A borrowing use case is depicted here as a librarian lending a book to a reader. In the figure, we include not only the use cases with the Reader and the Librarian, but also use cases of the Librarian doing Maintenance. This example is greatly simplified, and a real library system would have many more use cases.

In this UML use case, the stick figures represent the Reader and Librarian actors. The use cases are in the ovals, and the arrows represent an actor interacting with a use case.

In this section, we've just given a brief description of the early steps in a software project. A project of any size will require experienced developers to carry out the analysis and design required. Many of the early issues will require significant input from management.

Designing a large software system requires extensive participation from the most experienced analysts, designers, and programmers, and these three roles are often (but not always) quite distinct. As the size of the project shrinks, the more likely it is that all team members will participate in all phases of the project.

Even the smallest project can benefit from a certain amount of up front analysis and design. In the next section, we will go over some of the fundamentals of object-oriented analysis that any programmer should know and be able to use.

The Essence of Object-Oriented Analysis

Traditionally, Object-Oriented Analysis (OOA) has been used at the initial stages of a software project. The results of the OOA are then used for the next step of the development process, Object-Oriented Design (OOD). With the advent of more iterative methodologies, OOA is used to some extent for each development iteration, and the distinction between OOA and OOD can become blurred. A key aspect of OOA is that it should involve both the development team and the customer. It is the customer who best knows the problem domain, and what the system needs to do, while the development team best knows how to use programming and software resources to design a system that meets the customer's requirements.

In this section, we will cover some of the major aspects of OOA. There can be many different techniques, steps, or strategies used for OOA, some depending on which specific methodology is being used. The goal here is to provide you with an understanding of OOA that will help you to write better software.

Object Discovery

No matter which OO methodology is used, one of the most crucial aspects of the analysis and design is the determination of which objects and classes to include. Objects are at the core of any OO system, and it requires experience and judgement to determine just which objects belong in the system. Thus, one of the primary activities of OOA is called object discovery. The goal of object discovery is to find objects that can potentially become a part of the software system.

One of the first steps of the object discovery process is to examine the system specifications that are developed during the initial phases of the analysis. The level of detail of the specification will certainly vary depending on the size of the project. But even the smallest project should have some kind of written specification that can be used by the development team.

In this section, we will cover some general techniques that can be used for object discovery. Two sidebars, CRC Cards (page 93) and Use Cases (page 87), cover techniques that can be used alone or combined with the techniques discussed here to help with object discovery.

The first step of the analysis process is to use the written problem specification to determine likely candidates for objects used in the system. Objects are really instances of classes. During analysis, it is often easier to think in terms of specific objects rather than the more general concept of class. As the model of the system is refined, objects found in the analysis phase will be carried over directly to the design phase to create the architecture of the system, and most will eventually end up implemented as class definitions in real code.

One of the main differences between the analysis and design phases is the level of detail produced about the objects. Objects are treated at a much higher, less detailed level during analysis. The UML is especially effective at showing different levels of detail (see the sidebar on page 102). In the analysis phase, the UML can be used to show just a class or object. During design, the details of specific attributes, operations, and class relationships can be added.

When first starting out with object discovery, try to find as many candidate objects as possible. While finding too may objects can be a problem, it is probably better to find too many because you will be able to shorten and refine the list of candidate objects later. Just because you are using an object-oriented approach, it doesn't mean the customer knows or understands anything about objects.

The customer may not know exactly what they want or need, and their specification is not likely to be totally accurate or complete. It is common to find flaws in the customer's specification, and it is important to be able to work closely with the customer during the early stages of the project.

A good first pass at object discovery is to use the textual problem description to pick out the nouns and verbs. The nouns represent candidate objects, and the verbs represent candidate operations or methods that go with the objects.

Coming up with a definitive candidate list of objects is not a trivial task, and no two analysts or designers are likely to come up with the same list. Picking objects from the nouns and verbs in the description is a simple and direct way to get started. Often, once a few objects have been identified, it will be easier to find other candidate objects by additional examination of the problem specification.

The following is a list of things to look for that can help with object discovery:

As you go through this list, you will find that you will discover candidate attributes as well as candidate objects. In the early stages of analysis, it is not always clear if an item should be an object or an attribute. It is likely, however, that many items you identify in this process will end up as one or the other in the final model.

Once you have some candidate objects, it is important and useful to examine the responsibilities of the object. What function does an object perform, what are its responsibilities to the rest of the system? One way to look for responsibilities is to concentrate on the verbs in the problem statement. Sometimes discovering a function can lead to the discovery of several objects required to carry out that function.

Once you have some candidate objects, the techniques used with CRC cards can be useful. Not only do you focus on the responsibilities of a class, you also focus on collaborators of a class. Collaborators are other classes that use or are needed by a given class.

It can be useful to do some behavioral role playing or personification with a candidate object. Try to personify or imagine yourself as the object. Ask yourself questions. Whom do I interact with? How do I respond to a message from some other object? What is my job? What do I do? What do I contribute to the system? What do I need to remember? CRC cards are good for this because you can hold the card in your hand while you ask these questions.

The answers to these questions can provide clues to whether the candidate object is a good object or not, and possibly other classes needed to support its behavior. They can also lead to discovering methods needed to support an object.

Evaluate Candidate Objects

Once you have a list of candidate objects, it is important to evaluate each object. It is easy to get too many objects. You should remember inheritance, and look for the possibility of generalizing some of your objects into a higher level class. On the other hand, it is possible to try to group objects using inheritance when aggregation or composition is more appropriate. Use the is-a and has-a tests. And ask yourself if all the objects you have are really necessary. Could they be combined into a common class? In general, smaller is better.

There are some objective criteria that can be applied to objects to evaluate their ``goodness.'' Consider the following points:

CRC Cards

CRC Cards represent a simple yet useful OOA technique. CRC stands for Class-Responsibility-Collaborator. The idea is to generate a set of 3x5 or 4x6 index cards that include the classes that make up a system. Each card lists the name of the class, the responsibilities of the class, and other classes the class uses or collaborates with. These cards are usually generated in an interactive work sessions involving the customers, analysts, and developers. While a set of CRC cards might be used to eventually generate UML diagrams, they are really most useful for exploring interactions between classes in an open discussion format.

CRC cards were originally developed in the late 1980's by Kent Beck and Ward Cunningham as a technique to help procedural programmers move to the design perspective needed by object-oriented programming. In many ways, CRC cards are so easy and basic that their original paper still stands as a good reference. CRC cards have been found to be so useful that they have become a standard OO design tool.

What is a CRC Card?

The top line of the card specifies the name of the class in large letters. A class, of course, is a collection of objects - the things of most interest in the system being modeled.

The responsibilities of the class are listed on the left side of the card. A responsibility is a high-level description of what the class does. The responsibilities should be short, concise descriptions of what the class knows about, and what it does with that information. One of the points of the small CRC card is to force you to keep these descriptions small, and high-level. Any class should probably not have more than two or three responsibilities. Thinking in terms of responsibilities is important because it gets away from a data centric view of objects.

On the right side is a list of collaborator classes. These are classes that provide information or services for the class at hand. Each collaborator class will have its own CRC card. Note that a collaborator is a class that is used by the given class - the collaborator CRC card will not necessarily list the classes that use it. As more CRC cards are added, they can be laid out and physically grouped by classes that are related.

The backs of the CRC cards are often used to give more details of the data and methods needed to implement the class. These details can be used later when the classes are actually implemented in code.

Note that you are only allowed to write as much as will fit on the card. This helps your classes from getting too big or complex. The section in Chapter 5 on MVC has an example using CRC cards.

Using CRC Cards

CRC cards are quite flexible in how they can be used. They are often used in what is called a session. A CRC session can vary from an informal meeting with a couple of programmers to a formal session involving designated roles and scribes to officially record the session. Because it is an interactive process, a CRC session should not have more that 5 or 6 active participants (although passive observers are allowed as well).

CRC sessions often focus on a scenario - a sequence of interactions between objects suggested by a particular use case or part of the problem description. Cards can be placed on the table in groups, picked up and moved around. In using CRC cards to act out a particular scenario, the need for other classes or different responsibilities is often discovered. Many designers using CRC cards find it useful to use role-playing - picking up a card and moving it around, and essentially becoming that object.

As the session starts, one of the first goals will be to come up with some objects. The techniques we discussed in the Object Discovery section can be useful here to come up with some initial CRC cards.

Once the interactions and responsibilities of the different classes represented by the CRC cards have been explored, the information can be transferred to a more formal notation such as the UML. UML sequence and collaboration diagrams can be developed from the interactions discovered from using the CRC cards. UML class diagrams can be derived from the class information contained on a CRC card. Associations between classes can be used to build association diagrams. However, the main goal of using CRC cards it to gain understanding of the system and how it might work, not to produce UML diagrams. Many projects have used CRC cards as their chief documentation process.

Determine Object Hierarchies

Once you have a good list of candidate objects, the next phase of OOA is to organize the basic objects into hierarchies. Object discovery and hierarchy discovery often overlap. While examining a problem for objects, it will often be quite apparent that some objects naturally form a hierarchy. Other hierarchies will not be as obvious, and must be discovered explicitly.

The main goal of this phase is to identify the hierarchies that will take advantage of the OO paradigm. Remember the two major forms of hierarchies: generalization/specialization, or inheritance; and whole/part, or aggregation/composition.

The concept of inheritance with superclasses and subclasses is natural for many problems. Inheritance implies that a subclass will inherit the properties of the superclass, as well as adding new properties of its own.

Whole/part discovery often takes place after the initial object discovery. Sometimes the whole/part relationship is obvious, but sometimes it is helpful to look for whole/part hierarchies explicitly.

Hierarchies reveal how related or similar objects fit together. Objects also have relationships with other objects that reflect how the objects interact in ways other than inheritance and aggregation. For example, a relationship might indicate that one object uses the services or generates an instance of another class. A ``Customer'' object might generate an ``Order'' object.

Objects don't have to fit into a hierarchy. Sometimes a class, usually a simple one, is simply an attribute of another class. These are sometimes called helper classes.

Discover Object Attributes

So far, we have discussed finding objects and discovering object hierarchies and relationships. OOA must also examine object attributes. An object's attributes describe its meaning - what data it holds, what state it is in, what connections it has to other objects.

At the design level, objects generally have two kinds of attributes, public and private2. Public attributes are available to the world, while private attributes are used internally. Generally, during OOA, you are concerned primarily with the public attributes.

Imagining yourself to be the object works for attribute identification, just as it does for object identification as discussed earlier. Become the object in your mind and ask questions such as:

As you identify attributes that belong to your objects, you should consider where they belong in the class descriptions. With inheritance, the goal is to place attributes in a superclass as high up in the hierarchy as possible. If the same attribute can be used to describe members of different subclasses, then the attribute belongs in the superclass. Sometime attribute discovery will help you to revise and refine your class hierarchies.

Choosing Names

Choosing good names for things is an important part of the analysis process. In an object-oriented design, classes, objects, attributes, methods, and source code files are among the things that need names. The names you select should reflect the semantics of the item. Just as a good writer uses the English language carefully and effectively, a good programmer will carefully and effectively choose names.

Picking good names really does matter - in analysis, in design, and ultimately in coding. They convey meaning. Using a good name can eliminate the need for explanatory comments, for example. But this means your names must really reflect what the class represents or what the method really does.

Consider the following suggestions for picking names.

Follow the Java conventions for naming described in Chapter 2, especially regarding capitalization.

Classes should be named with common noun phrases, such as Color or Sensor.

Objects (the Java variables that reference objects) should be named to indicate they are specific instances with identity, such as theDoorSensor, foregroundColor, or listOfSensors.

Methods that modify the state of an object, or cause it to do something should be named with active verb phrases, such as drawShape or setColor.

Methods that return state information should indicate a result or use a form of the verb to be, such as getColor or isClosed.

As we noted earlier, the attributes discovered in the OOA phase are public attributes. At this point we assume that other objects will be able to access these attributes. The attributes reflect the model, and not the implementation design. In practice, you almost never make an attribute directly available to the outside world. Instead, you provide getter and setter methods to access the attributes.

Discover Object Operations

Methods are the services or operations a class defines to implement the behavior of member objects. Methods are how an object interacts with other objects in the system. Discovery of the methods used to implement object behavior is an important OOA activity.

Just as a class will have both private and public attributes, it will have private and public methods. During the analysis phase, method discovery will concentrate on public methods, and not those private methods used internally by a class.

There are several kinds of methods commonly associated with a class. Given the importance of messages in OO systems, the methods that respond to messages may be the most obvious to examine first. They define how other objects will interact with members of the class, and how they will respond.

Other objects often need to set or get the attributes of an object. Setter and getter methods are used to provide this access. During OOA, these are often implicitly assumed to be available for each public attribute.

While the relationships among classes and objects are often described statically during analysis and design, in practice the specific relationships between instances of objects are established dynamically as the objects execute. Classes need to provide methods used to build these connections. UML sequence diagrams (see the sidebar on page 99) are useful for understanding dynamic relationships among objects.

Remember that during OOA, you are working at a high level of abstraction. Thus, special operations like the constructor or other methods involved with implementation details should not be considered. These are details that can be filled in later in the design process.

You can use the above classifications to help you discover the methods that belong with a given class. Inheritance also makes a difference when describing methods. You will want to move methods that describe shared behaviors as high as possible in the hierarchy. You will need to identify methods that will be overridden by subclasses.

UML - Sequence Diagrams

Once you have some candidate objects, hierarchies, and behaviors identified, it can be useful to understand how the objects will interact with each other. The UML provides Sequence Diagrams to help this process. A sequence diagram shows example object instances horizontally with lifetimes that stretch vertically, with time flowing from top to bottom.

Sequence diagrams are often used in association with use cases - each scenario depicted in a sequence diagram. They are helpful for understanding just how objects will interact with each other during particular cases. For example, below is a sequence diagram for the borrow a book scenario given in the UML Use Case sidebar on page 87.

The boxes at the top (aReader, aLibrarian, theLibrary, aBorrowing) represent specific instances of objects. Note that a specific name (e.g., theLibrary) is used for the instance, and not a class name (e.g., LibrarySystem). The names typically use "a" or "the" to indicate some specific instance is being used.

The dashed vertical line below each is the time line. The open box around the timeline indicates the object is in an active operation. The horizontal lines with arrows represent messages to other objects. For example, aReader sends the borrow message to aLibrarian. When an object sends a message to itself, the arrow loops back on the object. Thus, theLibrary sends a message to itself to find the Reader because theLibrary has the list of readers. The dashed lines to the left represent returns. These need be supplied only when they add clarity to the diagram.

The three diagram types we've used so far, object diagrams, use case diagrams, and sequences, are the most useful UML diagrams, and the ones you will see most often. There are others, which are described in the sidebar "More UML" on page 113.

Note that this example is based on a specific scenario, and can be used to help with the final design - which operations will be needed, such as findReader, borrow, and so on.

In other cases, sequence diagrams are helpful to understand behavior of existing designs. We will use them to help clarify some of the Java Swing examples in Chapter 5.

The Essence of Object-Oriented Design

In the strict technical sense, Object-Oriented Design (OOD) takes the results of the Object-Oriented Analysis phase and produces a detailed design suitable for implementation in an object-oriented programming language. In some methodologies, especially those applied to very large software projects, this is close to what happens. However, for smaller projects, the exact line between analysis and design, and even between design and implementation, is not always that clear.

One of the main differences between OOA and OOD is the level of detail required. One of the goals of OOD is to refine the OOA candidate objects into real classes, define the operations and attributes, decide on specific data structures, and account for the target system.

Some OO methodologies separate design and implementation, while others blend the process somewhat, relying on quick feedback from the implementation to discover the inevitable design errors and provide quick feedback. While OOA is often done by specialized analysts, the designers and programmers are often the same people.

UML - Level of Detail

One of the reasons that makes the UML a good tool is that it can be used throughout the entire development cycle. The level of detail that you can view with class diagrams provides a good example.

During the analysis phase, the focus is at a higher level. This means less detail with the UML class diagrams. Thus, an analysis view of the shapes we discussed in Chapter 3 can look like this:

For the design phase, when the details become more critical, the UML allows complete specification of classes. The detail can be so complete, in fact, that some UML tools can generate code from the UML diagrams (and the other way around, too). So at the end of the design phase, the shape class diagrams can show a fine level of detail:

One of the current hot topics in object-oriented software development is just where to draw the lines between analysis, design, and implementation. In fact, there really is a different answer depending on the overall size of the project. The various development methodologies can be divided into two camps, more or less. For small to medium projects, those with 20 or less programmers and time frames of a year or so, there are several lightweight or agile methodologies. For larger projects with longer time frames, there are several heavyweight methodologies. Chapter 9 presents overviews of some of these methodologies. Which methodology to use is often a matter of the group or corporate personality.

Even though there are several specific methodologies to choose from depending on the size and characteristics of the software being developed, there are still some fundamental design principles that apply to any methodology, and the next section will cover some of these design basics.

Some Design Guidelines

Good design doesn't come easily. It usually requires considerable experience to be able to develop a good design. And it a certainty that two great designers are likely to come up with two equally good, but different designs for the same problem. And no two designers would come up with the same list of the most important design principles, although there would certainly be significant overlap.

This section presents some basic design principles. Most of these principles apply to the design phase, but many also apply to the analysis phase. Good design is good design, no matter the phase of development.

Probably the only way to get good at design is to design many programs, preferably with someone more experienced who can help you learn. It is not easy, and even the best and most experienced designers don't get their design completely right the first time. The following guidelines may not be complete, and certainly aren't the same ones someone else would pick, but they provide a good starting point.

Get The Big Picture

Before you can create a good design, it is important to have a good understanding of what the software needs to do, and what computing resources it will require to implement. Much of this understanding comes from the early planning phase.

Understand the Problem

One of the most important tasks is to understand the problem. This will often mean frequent talks with the customer. The customer needs to supply the experts necessary for the designers and programmers to understand the problem domain. A good analysis of the problem helps to improve understanding.

Understand the Target Environment

While the customer will be best at providing the information necessary to understand the problem domain, it is the programmers who will best understand the target computing environment. It is important to understand the limitations and features of the target computing environment. Sometimes, a software project will even require designing and specifying the hardware involved. The specific programming language used influences the ultimate design.

Think Objects

To get a good object-oriented design, it is critical that the developers think objects. If there are members of the development team that come from non-object oriented environments, they must learn to think objects.

Get Help

Even the best designers can't design great systems without help. The kind of help you need depends. It can include expert help to understand the problem domain, or help to understand specific hardware or software required for the project.

Encapsulation

If there is a single most important reason that object-oriented development works, it is encapsulation. Objects by nature encapsulate attributes and behavior, and encapsulation makes software more robust, easier to debug, easier to modify, and easier to maintain over the long term.

Maximize Encapsulation

The more independent each class can be, the better. Each class should not provide direct access to any of its internal attributes. It should provide the minimum number of methods for the outside world needed to carry out its responsibilities. The interface to the outside world should be designed to minimize the effects of any changes to the internal design of the class. In other words, you should maximize the encapsulation of all classes.

Minimize Coupling

As part of maximizing the encapsulation, you should minimize the coupling between classes. Classes should depend only on the public interfaces to other classes, and not rely on knowing anything about how the other class works. In cases where classes must be coupled by mutual responsibilities, the effects of the coupling should be minimized for the rest of the world.

Separate the GUI

Separate the implementation of the Graphical User Interface (GUI) from the implementation of the application's model. For example, the application must not rely on being able to dynamically retrieve values from GUI controls. Instead, changes in GUI values should update the internal state of the model. You should be able to completely replace the GUI without affecting the rest of the code. The MVC design pattern described in the next chapter helps to separate the GUI.

Designing Classes

Once the need for a class has been identified, these guidelines will help to improve the design of an individual class, or a group of related classes.

A Class needs a Purpose

Every class needs responsibilities. If there are no clear responsibilities and operations required by a class, then it likely should be a part of a different class. If the class doesn't have a purpose, it should not exist.

Classes vs. Attributes

If a class has a well-defined set of attributes that have associated operations that aren't really a part of the class, and that could potentially be used independently by other classes, then those attributes and operations are candidates for becoming an independent class. On the other hand, if there is a class with no operations, then its attributes may belong as simple attributes of another class. Because Java does not have the equivalent of a simple C structure, there may be classes in Java that really serve as structures, and thus may not require any operations, but be used as a simple data structure.

Associations vs. Inheritance

Be careful when designing objects with inheritance or aggregation. Frequently, designs will use inheritance when simple association or aggregation/composition is a better choice. Remember that all classes in an inheritance hierarchy must pass the is-a test. Don't confuse is-a with is-a-member-of. For example, A Circle is-a Shape, but it is-a-member-of a Drawing. Thus, a Shape should be a part of a Drawing, but not inherit from it.

A Class can't do Everything

Don't make classes too big. The responsibilities of a class should be just those that fit within that class, and should not be related to any other classes. If a class is trying to do things that really don't relate to its main responsibilities, then those behaviors likely belong in another class.

Inheritance

When designing an inheritance hierarchy, these guidelines can help yield better designs.

Is-A Test

All classes in an inheritance hierarchy must pass the is-a test.

Is-A Is Not Always Enough

The is-a test is not always enough. Names can mislead. It is possible for a class to pass the is-a test by using just the names of classes, but still not be a good subclass. Besides sharing a general name relationship, a subclass must share common behavior with the superclass. For example, while a room is a rectangular volume, it may not be proper to have a room inherit from a graphical cube class. Remember the is-a member test, as well.

Move Attributes and Operations as High as Possible

You should move attributes and operations as high as possible in the hierarchy. If you find two subclasses defining similar attributes or operations that aren't in the superclass, they may be better used if they are moved up to the superclass.

Don't Move Attributes and Operations Too High

Subclasses must take advantage of superclass. If most of the subclasses don't use operations defined in a superclass, or most are overriding the superclass definition, then the operation may not belong in the superclass.

Find Superclasses

If you have independent classes that have several similar attributes or operations, it is possible that those shared operations could be moved to a new common superclass. But be sure the new class has something to do.

General Guidelines

The Name Matters

Choosing good names for classes, attributes, methods, and variables is critical for writing good software. The names should be meaningful, and help explain the role of the item. Avoid abbreviations. A good descriptive name will reduce the need for explanatory comments. It may take a bit more effort to type a long name, but the savings in reduced commenting and ease of reading later will more than make up for a little one-time typing effort. Of course, a name that takes most of the line will hinder readability.

One Thing at a Time

The operations of a class should accomplish a single well-defined task. Don't combine a getter operation with one that changes the state of the object. Avoid side effects.

Don't Reinvent the Wheel

Avoid solving problems that have already been solved. Reuse existing code whenever possible. Use existing libraries and frameworks whenever possible. Learn about design patterns, and use them when appropriate.

You Won't Get it Right the First Time

No matter how good of a designer you are, you will not get the design just right the first time3. Recognize that there will be problems in the design, and fix them as soon as possible. Fixing problems will ultimately result in a better software system that is easier to modify and maintain. Learn about and use refactoring.

Simplicity

Make your design as simple as possible. On one level, this means don't try to adopt a fancy solution because you think it will work better - sometime a simple linear search will work as well as a fancier, but harder to code, binary search. On a different level, simplicity doesn't mean using the first thing that you can think of. Finding a simple, elegant design can require significant effort, but will pay off in the long run.

Your Software Won't Go Away

A software system is often used far beyond its expected lifetime. All software systems should be designed as if they will be used forever. This means designing for the long term. A well-designed system will be easier to maintain over time than one that takes shortcuts or makes assumptions about limited lifetime. Remember Y2K.

The Build and Release Phases

At some point in the development process, the planning process will lead to the building process. This crossover point will vary depending on the size of the project, and the methodology being used. And of course, the ultimate goal of any software project will be to release a running system to the customer.

Building the Software

For most programmers, writing code is what it is all about. Many would just as soon forget all the planning stuff, and write code. But experience shows that planning will lead to a better product. And a good design must be accompanied by good programming practices.

The build phase really consists of three major activities: writing code, testing the pieces of code being worked on (sometime called unit testing), and then debugging the code. This whole process can be greatly enhanced by choosing the right programming environment, and using the right tools.

Code Reviews and Ownership

One commonly followed programming practice that is valuable for any project is the code review. In a code review, a group of programmers will go over a piece of code line by line. Usually the author of the code is part of the review to help explain what the code does.

Code reviews can range from a small session with just two or three programmers involved, or may involve most of the programming team. Small, informal code reviews can take place almost any time during the development process, while more formal reviews knows as code inspections are an important part of the final quality acceptance process.

Regular code reviews are a very useful during development because people tend to overlook mistakes in their own code. They are just too close. Code reviews also help to spread expert knowledge throughout a development team. Individuals often have specialized knowledge that can be transferred to the team. The suggestions and ideas which evolve during review sessions often take the whole development process big steps forward.

One important rule that must be followed during a code review is to find ways to improve the code without being harsh or judgemental. Everyone involved in a code review can learn from the process, and the author should not be subject to ridicule or unkind words.

Having regular code reviews helps promote joint-ownership of all the code. While individual programmers may have primary responsibility for certain pieces of code, individuals really should not "own" code. Most programs consist of code contributed by many programmers. Eventually, these programmers will move on to other projects, and have to leave their code behind for the team to work with. Individuals should take pride in contributing a quality piece of code to the team, and not get possessive of code they've written. Having a team member who tries to maintain personal ownership of their code can lead to serious problems. Group ownership of all code is an important concept that team members must learn is important for the benefit of all.

Since you're reading this book, it is likely that you will be working in a Java based environment. As Java has matured as a programming language, it has in fact become the language of choice for a variety of applications. The language is truly object-oriented, and is a perfect target for an object-oriented design. The language is portable across the major computing platforms in current use, including the usually troublesome GUI components. A variety of programming tools is available to enhance the productivity of Java programmers. We will survey some of those tools in Chapter 10.

One of the recent trends in programming practice has been the emphasis on testing and integration as you go. The tendency in the past has been to independently develop fairly large pieces of the code with minimal testing, and then to integrate the parts late in the development process. There is considerable practical evidence that it is much better to get small pieces of the code working, perform unit tests as a standard part of the process, and to frequently integrate the different parts of the project.

The practice of constantly testing and integrating makes the traditional release the culmination of many small steps rather than a huge, final visible step in the overall process. Just as the lines between planning and building can get fuzzy, so to can the line between building and releasing. And, as usual, this will often depend on the magnitude of the project.

Releasing the Software

Traditionally, a finished piece of software is turned over to a testing department for final testing and ultimate release to the customer. This final testing is called functional testing, and usually takes a different team and tools than the unit testing used during ongoing development.

One of the most important tasks associated with the release of a system should be the learning phase. Every major project will yield any number of lessons - we used the wrong computer configuration, we used the wrong development tools, this testing and integration stuff really works, we didn't have enough programmers, or the pizza from Pizza Joe's is really bad. It is important that these lessons be used to constantly improve the overall work environment for the programming team.

Even if constant testing and integration are employed, there is still something special about the real final release date. For one thing, it often means lots of overtime right before. Sometimes, overtime work is unavoidable. However, this practice should be the exception and not the rule. In spite of the advances in understanding the software development process (which is in part what this book is all about), programming is still very much an art. Programmers really can't be productive working over 40 hours a week for more than a few weeks in a row, and they really need time off to recharge their creative energies. Unfortunately, this fact is still not recognized by many managers of programmers.

Once a project is released to the customer, it usually enters a new phase of its lifetime, the maintenance phase. Often, this means turning the finished code over to a maintenance team while the prime developers move on to other projects (which might be the new and improved version of the current project). Traditionally, the maintenance team consists of the newest, least experienced programmers. This tradition is not without some merit. For one thing, if the system has a good design, and the programmers produced good code, the maintainers will have a good example to learn from. But there should be close contact with the original development team and the maintenance team for as long as it is possible.

Eventually, however, most software takes on a life of its own, and it is likely that a significant portion of the original development team won't be around a few years down the line. It is this aspect of software reality that makes it all the more critical to use good development practices. It is here that object orientation can provide great paybacks.

More on the UML

We've been introducing various UML features as we've needed them. So far, we've used class and object diagrams, use case diagrams, and sequence diagrams. These diagrams are not the only UML diagrams available, although they are all we will use in this book. "More UML Diagrams" on page 113 gives brief descriptions of the other diagrams provided by the UML. These other diagrams are used to various degrees depending on the size of the project, and the design methodology used.

More UML Diagrams

In addition to the class and object diagrams, use case diagrams, and sequence diagrams already discussed, the UML also provides the diagrams briefly described in this section.

Collaboration Diagram

The Collaboration Diagram and the Sequence Diagram we've already discussed are both used to show object interaction. While the Sequence Diagram shows dynamic interactions, the Collaboration Diagram shows the static relationships and messages between objects. The Collaboration Diagram consists of objects, classes the objects belong to, links and messages with sequence numbers between these objects. Different developers tend to use one or the other of collaboration or sequence interaction diagrams.

State Diagram

The State Diagram is used when an object has well-defined states and serves as a complement to the class description. It shows all possible states that objects of the class can have, and which events cause the state to change or transition. The transition can also have an action connected to it, which specifies what should be done on transitions.

Activity Diagram

Activity Diagrams are used to show the flow of activities in a procedure. They show actions and the results of activities. Activity diagrams focus on the high level view of an entire process. They can also be used to help understand what is happening in a use case. Activity diagrams are useful for showing workflow and in describing behavior of system with parallel processing.

Deployment Diagram

A Deployment Diagram is used to represent physical relationships among the software and hardware components in a system. It can show how components and objects move around a networked system over.

Package

The UML Package symbol is used to group various UML items together into a single package. Various diagrams can be grouped such as classes, use cases, or collaborations. Then the relationships between various packages can be shown.

Chapter Summary

Resources

OOAD

Object-Oriented Analysis and Design With Applications, Grady Booch, Addison-Wesley, 1994, ISBN 0-201-54435-0. Still the classic OOAD reference.

CRC

The CRC Card Book, David Bellin and Susan Suchman Simone, Addison-Wesley, ISBN 0-201-89535-8.

A Laboratory For Teaching Object-Oriented Thinking, Kent Beck and Ward Cunningham, 1989, c2.com/doc/oopsla89/paper.html.

1
The steps of the traditional waterfall development cycle include: Feasibility Study, Analysis, Design, Coding, Testing, and Maintenance. One of the most important aspects of object-oriented methodologies is the recognition of the need for feedback and allowing for dynamic change in the design.


2
Protected can be thought of as a special case of private in this context.

3
This fact has led to the recent development of the Agile OO Methodologies, including XP. XP says that since a design will inevitably change, you should embrace change, and adopt an adaptive development process. See Chapter 9.


TOC PREV NEXT INDEX

The Essence of Object-Oriented Programming with Java and UML (DRAFT)

Copyright © 2001 by Addison-Wesley

All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. You may view this document on a web browser, but you must not mirror or make local copies.