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.
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.
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:
- Look at the problem space itself, together with any diagrams, pictures, and textual information provided by the customer.
- Look at other systems that communicate or interact with the system being modeled.
- Look at physical devices that will exist in the environment and interact with the system, regardless of the technology used to implement the system itself.
- Look at events that must be recorded and remembered by the system.
- Look at roles played by different people who interact with the system.
- Look at physical or geographical locations and sites that may be relevant to the system.
- Look at organizational units (departments, divisions, and so on) that may be relevant to the system.
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:
- Each object should have some data. You don't have to know all the attributes yet, but you should be sure that at least some attributes exist.
- If the candidate object has only one attribute, then perhaps that should be represented as an attribute of another object rather than a new object. There can be objects with only one, or even no attributes, but having only one serves as a flag for closer inspection.
- An object must do something to justify its existence, so you should be able to identify one or more methods for the candidate objects. It is very unlikely that an object will have no methods.
- At the analysis phase, the function and purpose of a candidate object should be independent of the hardware or software technology that will be used to implement the system. If it is not, then it isn't an essential object, but rather an implementation object that should wait until a later stage of development for more consideration.
- All the attributes of a proposed class should apply to all the objects in that class. If you find exceptions (e.g., this attribute applies in all cases except for that special object), then you may have combined subclasses when they should be part of a hierarchy. While you will want to combine classes as much as you can, it will sometimes be necessary to split a class into more specialized subclasses.
- Many of the "smells" applied to refactoring apply equally well to a design. See Chapter 8, Refactoring.
- Just as all the attributes should apply to every instance, all methods, or operations, should apply to all objects in the class.
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 an object X, in general, how am I described?
- How am I described in this problem domain?
- What do I need to know to carry out my function?
- What state information to I need to remember over time?
- What states can I be in as a member of class X?
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.
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.
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.
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.
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.
Chapter Summary
- All software can benefit from at least some design.
- There are several object-oriented development methodologies. All include some form of planning, building, and release phases.
- The initial design of a software project should be a collaboration between the customer and the developer.
- Object discovery and development of coherent object hierarchies are two of the major activities of OOA.
- CRC Cards and Use Cases are useful tools for software analysis.
- OOD is refining the results of the OOA. OOD takes the target environment into consideration, and turns the classes developed by the OOA into a design that includes most of the details of how the classes will be implemented.
- The overall magnitude of a project has great influence on the kind of methodology used, and the actual development practices that are used for the project.
- Continual coding, testing, debugging, and integration can enhance the development process, and lead to more robust and bug-free code.
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.
1The 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.3This 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.
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.