PART THREE Object-Oriented Design    

As we have stressed several times, the programming process consists of a problem-solving phase and an implementation phase. The problem-solving phase includes analysis (analyzing and understanding the problem to be solved) and design (designing a solution to the problem). Given a complex problem—one that results in a 10,000-line program, for example—it¹s simply not reasonable to skip the design process and go directly to writing Java code. What we need is a systematic way of designing a solution to a problem, no matter how complicated the problem is.

We¹ve used the term object-oriented design (OOD) frequently throughout this book, and we have employed OOD in an intuitive manner in the Case Studies. In this part of the chapter, we formalize and extend the technique that we've been using.  This methodology helps you create solutions that can be easily implemented as Java applications. The resulting applications are readable, understandable, and encapsulated. Java was developed in part to facilitate the use of the OOD methodology. In the next two sections, we present the essential concepts of OOD; throughout the rest of the book, we will expand our treatment of this approach.

Object-oriented design  A technique for developing software in which the solution is expressed in terms of objects -- self-contained entities composed of data and operations on that data that interact by sending messages to one another

Objects and Classes Revisited

Let¹s review what we have said about objects and see how they work in the context of programming. Then we can more effectively explore how to solve a problem with OOD.

What is an object? We have defined an object in three ways:

·       a collection of data together with associated operations,

·       an entity or thing that is relevant in the context of a problem, and

·       an instance of a class.

What is a class? We have also defined a class in three ways:

·       a description of an object that specifies the types of values it can hold and the operations that it can perform,

·       a description of the behavior of a group of objects with similar properties, and

·       a pattern for an object.

Although varied, these definitions are complementary rather than contradictory. In the problem-solving phase, we look for objects (things) that are relevant to the problem at hand. We analyze these objects and see how they interact. We abstract the common properties and behaviors from these real objects and define classes that describe this behavior. In the implementation phase, we then use these descriptions (classes) and the syntax of our programming language to define classes (in the Java sense) that describe the data values that an object can have and the operations that it can perform. Our application instantiates objects of these classes. which then interact to solve the original problem.

As you should recognize by now, a class isn¹t an object, but rather defines a pattern to use in creating a specific kind of object. For example, in Chapter 2 we defined a Name class. Once we define the class, we can declare a variable of the class Name, such as testName, instantiate an object with new, and assign the object¹s address to testName. Here is some example code that illustrates the process:

// Define a class

class Name

{Š}

Š

Name testName;

Š

testName = new Name("Herman", "Herrmann", "George");

Think of the class definition as analogous to a set of blueprints. A blueprint isn¹t a house, but it provides the information needed to construct a house. A class isn¹t an object, but it provides the information needed by the computer to construct an object. Declaring a variable is like putting an empty page in your address book in anticipation of having an address for the house. You don't yet have the address because the house hasn't been built.

The new operator invokes the constructor method for the class; it is analogous to calling in a construction crew to interpret the blueprints and actually build the house. Once the house is built on a lot, it has an address, and you can then write this address on the blank page in your address book (assign the address of the object to the variable). Figure 7.8 illustrates this analogy.

Artbox  Figure 7.8  An Analogy Illustrating the Relationships among a class, a Variable, and an Object,  formerly Fig. 6.1 located at the bottom of p. 272

Collaboration Between Objects

Now we turn to the process of developing a problem solution using OOD. Our goal is to develop a design that captures the information needed to program a solution to a problem. But the design should not be at the level of detail of an actual program. We want a way to write down a solution without becoming distracted by programming language syntax. As we have said before, it is important to think first and code later.

OOD focuses on the entities (objects) in a problem. Using this approach, we solve a problem by identifying the objects that make up a solution and determining how those objects interact. The result is a design for a set of objects that cooperate to solve a problem.

We begin by brainstorming a set of objects that we think are important in a problem. Then we consider some scenarios in which the objects interact to accomplish a task. In the process of envisioning how a scenario happens, we identify additional objects and interactions. We keep trying new scenarios until we find that our set of objects and their interactions is sufficient to accomplish any task required by the problem.

How do we get from objects to classes? If we notice that the same kinds of objects appear in multiple places, we identify their common properties and define a class that represents them. For example, if an application has objects for a home phone, a business phone, and a cell phone, we would create a Phone class and use it to instantiate these objects.

Each class has a set of responsibilities, which are the actions that its objects support. Objects collaborate with each other: One object may ask another object to carry out one of its responsibilities. The collection of Classes, Responsibilities, and Collaborations (CRC) works together to solve a problem. As we've seen, responsibilities are implemented in Java by methods, and collaborations are implemented by method calls.

Responsibility  An action that an implementation of an object must be capable of performing

Collaboration  An interaction between objects in which one object requests that another object carry out one of its responsibilities

Up until now, our classes have been designed without formally considering object collaborations. In more complex settings, when you call an instance method, it may it may in turn make other calls. Thus, a wide range of collaborations may take place in the background before the method finally returns. By considering how objects will cooperate, we are able to design an interface that serves the needs of all of its clients. In the next section, we see how to use a notation, called a CRC Card, to keep track of classes, responsibilities, and collaborations.

Background Information: What's the Best Solution?

As we discuss OOD, keep in mind that there are many different correct solutions to most problems. The techniques we use may seem imprecise, especially when contrasted with the precision required by the computer. In fact, the computer merely demands that we express (code) a solution precisely. The process of deciding which particular solution to use involves the skills of judgment and reasoning. It is our human ability to make choices without having complete information that enables us to solve problems.

For example, in developing a simulation of an air traffic control system, we might decide that airplanes and control towers are objects that communicate with each other. Or we might decide that pilots and controllers are the objects that communicate, as shown in Figure 7.9. This choice affects how we subsequently view the collaborations and hence the responsibilities that we assign to the objects. Either choice can lead to a working application. We may simply prefer the one with which we are most familiar (recall the ³Look for things that are familiar² strategy from Chapter 1).

Figure 7.9 Alternative Choices of Objects in an Air Traffic Control Application   

New art. Two panels, side by side. On left, drawing of an airplane and a control tower with a zigzag line between them. Put tower in lower left of panel and plane in upper right. In right panel, drawing of a tower controller and an pilot, with same zigzag line between. Controller in lower left, pilot in upper right. Draw enough context around them (interior view of tower and plane) to show where they are while communicating.

Some of our choices lead to designs that are more or less efficient than others. For example, keeping a list of names in alphabetical order rather than random order makes it possible for the computer to find a particular name much faster. Choosing to leave the list randomly ordered still produces a valid (but slower) solution.

Other choices affect the amount of work that is required to develop the remainder of a problem solution. In creating an application for choreographing ballet movements, we might begin by recognizing the dancers as the important objects and then create a class for each dancer. In doing so, we discover that all of the dancers have certain common responsibilities. Rather than repeat the definition of those responsibilities for each class of dancer, we can change our initial choice: we can define a class for a dancer that includes all the common responsibilities and then develop subclasses (the subject of Chapter 9) that add responsibilities specific to each individual dancer. Figure 7.10 illustrates the relationship between these classes.

Figure 7.10 Dancer Class with Subclasses for Specific Dancers  AIT  Dancers located at the bottom of p. 279

The point is this: Don¹t hesitate to begin solving a problem because you are waiting for some flash of genius that leads to the perfect solution. There is no such thing. It is better to jump in and try something, step back and see if you like the result, and then either proceed in the same direction or make changes. The CRC card technique is a way to easily explore different design choices and keep track of them.

The CRC Card for Design Notation

There are three basic steps in developing an OOD. We¹ve actually been using these steps in the Case Studies. The first step is brainstorming a list of possible objects. The second step is to filter this list, eliminating duplicates or objects that aren¹t really appropriate for the computer to implement. The third step is to identify the responsibilities for the reduced list of objects.

Up to now, our Case Study problems have been simple enough that we can merely list the obvious responsibilities for a new class. But in a problem that involves multiple classes, we must consider how they collaborate. To do this, we pick a set of processing scenarios that cover the essential tasks that the application will perform. We hand-simulate these scenarios, using CRC cards to keep track of classes, collaborations, and responsibilities that we identify along the way. That is, we pretend to be the objects, and we go through the steps required to carry out some portion of the problem¹s solution.

After gaining some experience with a few initial scenarios, we identify additional scenarios that should be tried. We keep doing this until we run out of ideas for scenarios or are convinced that we¹ve covered all of the necessary objects and responsibilities in our design.

Now we look at each of these steps in turn.

 Identifying the Initial Classes Through Brainstorming

The first step is brainstorming an initial set of classes for the problem. As we have said on numerous occasions, a team of programmers typically writes large programs. Team brainstorming is a bit different from what we do as individuals. It seeks to encourage freethinking among the team members. Each person identifies whatever objects he or she sees in the problem and then proposes classes to represent them. The proposed classes are written on a board. None of the ideas for classes are discussed or rejected in this first stage.

For example, suppose we have the following problem statement: Create an application for a veterinarian that produces a data file for a dog. The data file will be used by other applications, such as printing immunization reminder letters, or an AKC registration application. The application must input the essential information about the dog and output the data to a file. Either an owner or a breeder will bring a puppy (sometimes an older dog) in for its first exam. The receptionist gives a form to the owner to fill out while waiting and then enters the data during the exam. When the owner leaves, he or she receives a printed birth certificate along with a packet of information about dog care.

The data entry program is just the first step. You will also be writing the other applications that use the file, and the veterinarian has several ideas for how health history data can be associated with the file.

We begin by looking at the existing paper form. We then brainstorm with our teammates about what we see there. We also consider that we will be writing several more applications, and so the classes we develop here must be general enough to be reused. Here is the list of potential objects:

Dog

Puppy

Owner

Receptionist

Veterinarian

Form

Dog Name

Dog Breed

Color(s)

Gender

Immunization Reminder

Registration Form

Health History

Owner Name

Owner Address

Owner Phone

Some of these items are clearly not a part of our solution, such as the receptionist. The purpose of brainstorming, however, is to generate ideas without any inhibitions. Once we¹ve run out of ideas, we move on to critiquing them.

Filtering

After brainstorming, we filter the classes. First, we eliminate duplicates. Next, we decide whether each class really represents an object in the problem. The team then looks for classes that seem to be related. Perhaps they aren¹t duplicates, but they have much in common, and so they are grouped together on the board. For example, we have a Dog name and an Owner name. How do they differ? They don't. We can use the same class to represent both.

At the same time, the discussion may reveal some classes that were overlooked. For example, the receptionist is not an object in the application, so we delete that item from the list. But that reminds us that we need to provide a user interface for data entry. Recall that in Chapter 1, we defined an interface as a connecting link that allows communication. We also defined an interactive system as one that supports direct communication between the user and the computer. A user interface is that part of a program that allows the user and the computer to communicate interactively. Informally, it is the part of the program that is visible to the user.

 

User Interface that part of a program that allows the user and the computer to communicate interactively

 

For each class that survives the filtering stage, we create a CRC card. The CRC card is just an index card with a line drawn vertically down the middle. The name of the class is written at the top and the two columns have the headings Responsibilities and Collaborations. Figure 7.11 shows a blank CRC card.

Artbox  Figure 7.11  A Blank CRC Card,  located at the top of p. 279   Formerly Fig. 6.3

We have provided spaces at the top of the CRC card for naming the superclass and subclasses of the class. These items are discussed in Chapter 9. Recalling our example of the choreography application, the different kinds of dancers would be subclasses of the dancer class. Each specific dancer class would have the dancer class listed as its superclass. Filling in these spaces helps us to keep track of these relationships between our classes.

Let¹s filter the list we just generated for the dog record. As we've noted, we don't need the receptionist, but we do need a user interface. Do we really need both Dog and Puppy? Puppies quickly become dogs, so we can just keep the Dog class. In fact, all of the information pertains to a dog, so this will be the main class in the application.

 Is the Owner an object? Of course the person isn't really an object. But the form represents the owner with a name, address, and phone. These should be grouped together as an abstraction, so we can define class Owner to hold this information. What about the Veterinarian? There's only one veterinarian at this clinic, so it's redundant to record her name on each record. However, we may revisit this decision in the future if she decides to expand the practice by hiring another vet.

The Form isn't really a part of the application, but it can be a model for the steps in entering the data. It is much easier for the receptionist to enter the data if the program requests it in the order that it appears on the form. The Immunization Reminder, Registration Form, and Health History are parts of other applications. Here is our revised list, indented to show objects that are contained within other objects.

User Interface

Dog

Name

Gender

Breed

Color

Owner

Name

Address

Phone

 

For each of these classes, we create a CRC card. All of them? Well, no, not all of them. CRC cards are notational devices to help us in the design phase. Gender, Breed, and Color are objects in the sense of the problem. But a little thought reveals that they don't have any responsibilities other than storing information that describes a dog. Thus, they should not be represented by a class, and there is no need to create CRC cards for them.

In doing coursework, you may be asked to work individually rather than in a collaborative team. You can still do your own brainstorming session and filtering. However, we recommend that you take a break after the brainstorming session and do the filtering once you have let your initial ideas rest for a while. An idea that seems brilliant in the middle of brainstorming may lose some of its attraction after a day or even a few hours.

 Determining Responsibilities

Initial Responsibilities  Once you (or your team) have identified the classes and created CRC cards for them, go over each card and write down any responsibilities that are obvious. The Name class has a responsibility to get its first name, its middle name, and its last name. We would list these three responsibilities in the left column of its card as shown in Figure 7.12. In our scenarios, we may discover that our existing Name class has all the responsibilities that we require, and so we can just reuse it.

Artbox  Figure 7.12  A CRC Card with Initial Responsibilities, Formerly 6.4 located at the top of p. 281. Change "Know" to "Get" 3X

Scenario Walk-Through  To further expand the responsibilities of the classes and see how they collaborate, we carry out processing scenarios by hand. This kind of role-playing is known as a walk-through. It is a different process than the algorithm walk-through discussed in Chapter 4. An algorithm walk-through is intended to verify an algorithm. A scenario walk-through is intended to explore potential solutions to a problem. We ask a question such as, ³What happens when the receptionist wants to create a new dog record?² We then answer the question by explaining how each object is involved in accomplishing this task. In a team setting, the cards are distributed among the team members. When an object of a class is doing something, its card is held in the air to visually signify that it is active.

To answer the question we need to make a decision. What are the responsibilities of User Interface and Dog for getting the data? Should User Interface prompt for and read the data, or should Dog? The team decides that User Interface should do the input, because the Dog class will be used elsewhere, and other applications may need to input dog information differently. So the person with the User Interface card holds it up and says, ³I have a responsibility to get the dog¹s information from the user.² That responsibility is written down on the card. Once the data is input, the User Interface must collaborate with class Dog to construct a Dog object and output the information to a file.

Figure 7.13 shows a team in the middle of a walk-through. Figure 7.14 shows partially completed CRC cards for User Interface and Dog

Artbox  Figure 7.13  A Scenario Walk-through in Progress, Formerly 6.5 located at the top of p. 282

User Interface says, ³I need to collaborate with Name to construct the Dog's name.² Name says, ³I have the responsibility to construct a Name object. This is already on my card, so I¹m done.² The Name card is then lowered. User Interface says, ³I get some more data, and I need to collaborate with Name again to create a name for the Owner.² We've already seen that Name has this responsibility, so User Interface goes on to say, "I need to collaborate with Address to create an address for the owner. " The Address card is held up, and because its initial responsibilities include a constructor to build an address, its job is done and it can be lowered.

New Artbox  Figure 7.14  the CRC Cards for User Interface and Dog

This scenario proceeds until all of the data values have been entered and stored on the file. When the last card is lowered because all of the work is done, the scenario ends.

Reading about the scenario makes it seem longer and more complex than it really is. Once you get used to role-playing, the scenarios move quickly and the walk-through becomes more like a game. However, to keep things moving, it is important to avoid becoming bogged down with implementation details. Dog should not be concerned with how Gender is implemented. Address doesn¹t need to think about whether the ZIP code is stored as an int or a String. Merely explore each responsibility in enough depth to decide whether a further collaboration is needed or if it can be solved with the available information.

Subsequent Scenarios  We began the first scenario with a ³What happens when . . .² question for the most obvious case. The next step is to brainstorm some additional questions that produce new scenarios. Consider the following list of some further scenarios for our dog record example:

What happens when the user

         wants to correct a value that has already been entered?

         wants to skip entering a value that isn't filled-out on the form?

         wants to look at a record that has already been stored on a file?

         wants to enter all the puppies from a litter, changing only a the name, gender, and color for each one?

We walk through each of the scenarios, adding responsibilities and collaborations to the CRC cards as necessary. After several scenarios have been tried, the number of additions decreases. When one or more scenarios take place without adding to any of the cards, we brainstorm further to see if we can come up with new scenarios that may not have been covered yet. When all of the scenarios that we can envision seem to be doable with the existing classes, responsibilities, and collaborations, then the design is finished.

When our design is complete, we can implement the responsibilities for each class. The implementation may reveal details of a collaboration that weren¹t obvious in the walk-through. Of course, knowing the collaborating classes makes it easy to change their corresponding responsibilities. The implementation phase should also include a search of available class libraries to see if any existing classes can be used. For example, we see that the java.util.GregorianCalendar class represents a date that can be used to implement Date Of Birth.

To summarize the CRC card process, we brainstorm the objects in a problem and abstract them into classes. Then we filter the list of classes to eliminate duplicates and unnecessary items. For most classes, we create a CRC card and list any obvious responsibilities that it should support. We then walk through a common scenario, recording responsibilities and collaborations as they are discovered. Next, we walk through additional scenarios, moving from common cases to special and exceptional cases. When it appears that we have all of the scenarios covered, we brainstorm additional scenarios that may need more responsibilities and collaborations. When our ideas for scenarios are exhausted and all the scenarios are covered by the existing CRC cards, the design is done.

Attributes and Their Internal Representation

The CRC card describes the formal interface to a class. The information recorded on the CRC card, together with descriptions of what each of the responsibilities does, becomes the written specification for the class. It is this information that a potential user of the class sees, not the source code of the class.

We know what methods we need to implement and what they need to do. However, before we can begin developing algorithms for the methods, we need to determine the information contained in a class; that is, the contents of a class. We can call the information contained in a class its attributes.

Attributes  The information contained in a class, or an object, which is used in carrying out its responsibilities.

Look at the following CRC card for a Phone class. Notice that it shows the responsibilities of the class, but not its attributes.

AIT  Class interface design CRC card, located at the bottom of p. 289  Change "Know" to "Get" 2X

Some of the attributes may be obvious, and others may have emerged during the scenario phase. Such attributes are often written on the back of the CRC card. When we start implementing the algorithms for the responsibilities, we may realize that we need additional attributes in order to carry out the responsibility.

Once the attributes have been established, the next step is to decide on their internal representation. The internal representation of an attribute may be a simple data member or another class. Our goal in selecting an internal representation should be to simplify the implementation of the responsibilities and to make the object efficient in terms of storage space and execution time. These goals sometimes conflict, and we must balance simplicity against efficiency.

Rarely will you have to invent an entirely new data representation. Most programs are written to solve problems with which people have dealt in the past. Consider who would ­normally use such data, and consult with those people or look in books that they would use (Figure 7.15). For example, astronomers use dates in computing the positions of planets over the centuries. You can find the formulas for computing the Julian day in some astronomy texts.

Artbox  Figure 7.15  Who Would Use Similar Data?  Formerly 6.8, located at the top of p. 292

It should be clear that we can't provide a set of rules that will automatically lead you to an internal data representation for a class. Each situation you encounter is different. Be prepared to give this part of your design some careful thought, to go to a library and do some research, to consult with other people, and to trade off issues of efficiency and complexity.

Now back to our Phone class. The CRC card for Phone externally represents the area code and number as numeric values, because the "Get" responsibilities return them as int values. Internally, this information could just as easily be kept as strings. Is one representation better than another? We can¹t answer that question without knowing how the class might be used in a problem. However, the beauty of abstraction and encapsulation is that the internal representation can be changed and the program that uses the class remains oblivious to the modification. Let's stick with the numeric representation.

Now we can write the responsibility algorithms of the Phone class. We must convert the responsibility names written as phrases into Java method identifiers. ³Create itself² becomes the class constructor Phone. ³Get area code² becomes the observer getAreaCode, and ³Get digits² becomes an observer called getDigits. ³Number as digits² becomes toDigits, and ³Number in print form² becomes toString. ³Are two numbers equal² becomes equals(Phone secondNumber).

The algorithms for the observer responsibilities are so simple that we can go straight to writing code. What about toDigits, the method that returns the number as 10 digits? We can multiply the area code by 10,000,000 to shift the digits over and add the result to the number. How do we convert the area code and number to strings and insert hyphens as separators? Hyphens! Exactly how is the number as a string supposed to look? We often see a telephone number written with two hyphens as follows: 512–411–2323. Thus we have to break the number portion into two pieces: the first three digits and the last four digits. We can use integer division and remainder to accomplish this separation.

To compare two phone numbers, we first compare the area codes. If they are not the same, the numbers are not equal. If the area codes are the same, we then compare the numbers. If both the area codes and the numbers are the same, the method returns the value true. Here is the code for the Phone class, based on the CRC card design and the chosen attribute representation. Note that we have included it in package utility.  We have also added implements Serializable in case we might want to output it to as file as n object.

 

package utility;

inport java.io.*;

public class Phone implements Serializable

{

  private int areaCode;

  private int digits;

 

  public Phone(int area, int number)

  // Constructor

  {

    areaCode = area;

    digits = number;

  }

 

  public int getAreaCode()

  {

    return areaCode;

  }

 

  public int getDigits()

  {

    return digits;

  }

  public long toDigits()

  // Returns area code and number as a sequence of 10 digits

  {

    return (long)(areaCode * 10000000L + digits);

  }

 

  public String toString()

  // Returns area code, hyphen, first three digits of the number, hyphen,

  //   and last four digits of the number

  {

    String result;

    long firstThree;                          // First three digits of the number

    long lastFour;                            // Last four digits of the number

    firstThree = digits / 10000;

    lastFour = digits % 10000;

    result = new String(areaCode + "-" + firstThree + "-" + lastFour);

    return result;

  }

 

  public boolean equals(Phone secondNumber)

  // Returns true if the phone numbers are the same; false otherwise

  {

    if (areaCode != secondNumber.getAreaCode())

      return false;

    if (digits != secondNumber.getDigits())

      return false;

    else

      return true;

  }

}

 

Implementing Encapsulation

We have now finished implementing the Phone class. We have encapsulated it physically by making the data fields private. The class is immutable: There are no transformers that change the attributes. We have encapsulated it visually by including it in a package. However we are not yet finished; we need to provide the formal interface for the class--the written specification that the user sees. There are two rules about the content of the interface. It must cover all the information necessary to use the class and it must not give away any internal class details. A good approach is to use the CRC card description, substituting the actual method headings. Table 7.1 contains the external interface for class Phone.

 

Class Phone provides a basic telephone object. The constructor allows creation of a phone from two integer numbers, representing area code and number.  This class implements Serializable.

public Phone(int area, int number)        // Constructor

public int getAreaCode()                  // Returns area code as an integer

public int getDigits()                    // Returns digits in the number as an

                                          //  integer

public long toDigits()                    // Returns area code and number as a

                                          //  sequence of 10 digits

public String toString()                  // Returns area code, hyphen, first three

                                          //  digits of the number, hyphen,

                                          //  and last four digits of the number

public boolean equals(Phone secondNumber) // Returns true if the phone numbers are

                                          //  the same; false otherwise

 

Table 7.1  External Interface Documentation for Class Phone

 

Many Java programmers use a tool called javadoc to generate the interface documentation as web pages. To use javadoc, you must write a set of specially formatted comments in your code, and then run the code through the javadoc tool, much as you run it through the compiler. But instead of outputting Bytecode, javadoc outputs web page code called HTML. Learning to write javadoc comments is like learning another programming language, so we do not explore them any further in this text.

 

Software Engineering Tip

Documentation

As you create your object-oriented design, you are developing documentation for your code. Documentation includes the written problem specifications, design, development history, and actual code.

Good documentation helps other programmers read and understand your code and can prove invaluable when software is being debugged and modified (maintained). If you haven¹t looked at your code for six months and need to change it, you¹ll be happy that you documented it well. Of course, if someone else has to use and modify your program, good documentation is indispensable.

Documentation is both external and internal to the code. External documentation includes the specifications, the development history, the design documents, such as CRC cards, and the interface specification. Internal documentation includes code formatting and self-documenting code—meaningful identifiers and comments. You can use the pseudocode from your design as comments in your code.

This kind of documentation may be sufficient for someone reading or maintaining your applications. However, if an application will be used by people who are not programmers, you must provide a user¹s manual as well.

Be sure to keep your documentation up-to-date. Indicate any changes you make in the code in all of the pertinent documentation. Use self-documenting code to make your programs more readable.

Self-documenting code  Program code containing meaningful identifiers as well as judiciously used clarifying comments

 

 

UML Diagrams

It can be difficult to understand the operation of an application just by looking at the written documentation. When many classes are collaborating, keeping track of their relationships involves building a mental picture. Before long, we start drawing diagrams, just to keep it all straight. Perhaps we use boxes to represent classes, and lines between them to represent collaborations. Inside the boxes, we make notes about the classes. Then we start using solid and dashed lines to indicate different kinds of collaboration. Eventually, we have a diagram that captures all of the important structural information about the application. The trouble is, nobody else knows what our diagram means! If programmers used a common set of symbols, then they could read each other's diagrams.

 The Unified Modeling Language (UML) is just such a collection of symbols. It was developed specifically for diagramming object-oriented applications. Even though it has the word language in its name, UML is not a programming language. It is just a collection of graphical symbols. And it is unified in the sense that it is made up from a combination of three different sets of symbols that were in use prior to its development. Each of those earlier conventions had different strengths and weaknesses, so UML was created to provide a single set of symbols incorporating all of their best features.

The UML symbols are divided into subsets that are used for different purposes. For example, the Use-Case symbols could be used to identify scenarios for the CRC card process: Stick figures of people represent users and what they want to do with the application. A UML Collaboration diagram charts the collaborations that take place during a scenario walk-through. Activity diagrams show the steps that carry out a responsibility. There are UML diagrams for documenting just about every aspect of programming, from analyzing the problem to deploying the code on customer's computers. You could take an entire course in drawing UML diagrams! In this text, however, we are going to use just one specific part of UML: the Class diagram.

A class is represented in UML by a box that's divided into three sections. The top section has the name of the class. The middle section lists its attributes, and the bottom section lists responsibilities. Here's a UML diagram of the Phone class:

 

Phone

-areaCode: int

-digits: int

+Phone(area: int, number: int)

+getAreaCode(): int

+getDigits(): int

+toDigits(): long

+toString(): String

+equals(secondNumber: Phone): boolean

 

As you can see, the diagram lists the attributes and their types in the middle section. The minus sign in front of each attribute is shorthand for private. UML uses + to mean public, and ~ to mean package-level visibility. You can see that all of Phone's responsibilities are listed in the bottom section, showing parameters and return types for value-returning methods. As you can also see by the plus signs, Phone's responsibilities are all public.

This diagram gives us a compact summary of the class interface. We can put several of these on a page, and draw lines between them to indicate their relationships. For example, a collaboration is indicated by a line with an arrow. The arrow shows that one class uses a responsibility of another class. You can also add a note next to the arrow to indicate how the calling class uses the collaborating class. For example, the following diagram shows a User Interface class that collaborates with System to read a file name. Here we just show the name of each class and the relevant operation.

 


 

 


When a class contains an instance of another class, such as the Name class contained in our Dog class, we draw a solid diamond at the end of the line next to the containing class.


 

 


UML provides graphical conventions for representing other kinds of relationships between classes, but these are sufficient for our immediate purposes. In later chapters, we introduce additional symbols as necessary.  Before we leave this section, look at the UML representation of the TrackTeam objects in the Case Study in Chapter 5.  See how the relationships become clearer using this graphical technique.


 

 


case Study Dog Record ENTRY

Problem: Develop the application for the veterinarian that was described earlier, to put a record for a dog on a file.

Brainstorming and Filtering: We¹ve already started this process, having brainstormed the initial set of classes and filtered them. After the initial scenario walk-through, we have the following list:

User Interface

Dog

         Name

Gender

         Breed

         Color(s)

         Owner

                           Name

                           Address

                           Phone

The realistic nature of this problem means that it has a large number of classes. We don't need to go through the implementation of all of them to illustrate the design process. We can work with a subset: Dog, Name, Gender, and Owner (with Name and Phone) are sufficient to establish the basic pattern. You can follow this pattern in adding the other classes as part of the exercises.

 

Determining Responsibilities: We've already decided that the User Interface will be responsible for getting the data for the dog record. To do this, it must collaborate with a class that does screen I/O. Because all of these are simple values, we can use JOptionPane. Let's look at a scenario of entering a dog record, and consider how User Interface collaborates with Dog.

               There are two approaches to orchestrating this collaboration. We can make Dog immutable, in which case, User Interface must gather together all of the data for a Dog, and pass it to a constructor. The other option is to make Dog mutable, and provide methods to set its values. Then we can instantiate an empty record and fill it as we input the data.

               If we were using the Dog class only for this application, the first approach makes sense. But knowing that it will be imported into other applications, which could include updating the data, we'll be glad we it mutable.

               For each field on the form, User Interface gets data from the user, and collaborates with Dog through a responsibility that sets an attribute to that value. User Interface must collaborate with classes Name, Gender, Owner, and Phone, to instantiate the necessary objects to pass to Dog.

               Once all of the values are input, how do we output the Dog record to the file? A Dog object should provide a responsibility that outputs its data to a specified file. But who uses this responsibility? Does the User Interface tell the Dog to output itself? It could, but let's have the User Interface just be responsible for providing a Dog record. We can have the driver for the application call the Dog 's output responsibility. Figure 7.16 shows the CRC card for User Interface.

 

Class Name: User Interface

Responsibilities

Collaborations

Input Dog

       returns Dog

JOptionPane, Name, Gender,

Owner, Dog

Figure 7.16  CRC Card for User Interface Class

 

What are the responsibilities of the Dog class? We know it must have a constructor to create an empty record, and transformers to set the individual attributes. In addition, it must have a responsibility that writes to a file. Is there anything else? This is where we need to consider hypothetical scenarios for its use in other applications.

               What if another application will input a Dog record? What if it wants to observe the individual attributes of a Dog record? We should at least provide file input for a previously stored record, and a set of observers. We've listed three attributes for a Dog record (Name, Gender, Owner). This means that there are three basic observers and three transformers.

The file input responsibility could be either a constructor or a transformer. That is, it could input a dog record and return a new Dog object, or it could take an existing Dog object and replace its contents with the values read from the file. Let's make it a transformer so that we have the option of not creating a new object for every input.

               We now have a list of nine responsibilities for a Dog object. This number is actually fairly typical for an object with this many attributes. Having many simple responsibilities is the price we pay for providing a flexible interface to an encapsulated class. Our reward is a class that is written once, and can be reused in many other places. Let's summarize these observations in the CRC card for Dog.

 

Class Name: Dog

Responsibilities

Collaborations

Create()

Get name

Get gender

Get owner

Set name

Set gender

Set owner

Write to file (outFile)

Read from file (inFile)

Name, Gender, Owner

Name

Gender

Owner

Name

Gender

Owner

ObjectOutputStream

ObjectInputStream

 

Note that we have used ³set² followed by the field name as names for the transformers. This is common Java terminology: ³get² as a prefix for the field observers and ³set² as a prefix for the field transformers.

Additional Scenarios: Earlier, we discussed some ideas for additional scenarios:

What happens when the user

         wants to correct a value that has already been entered?

         wants to skip entering a value that isn't filled-out on the form?

         wants to look at a record that has already been stored on a file?

         wants to enter all the puppies from a litter, changing only a the name, gender, and color for each one?

 

         How should we handle the correction of a value that has been entered? There are several possibilities, such as displaying a dialog asking if the user wants to change what was just entered. But for now we'll take the simplest approach, which is to not allow changes, and promise to create a separate application for updating an existing record.

What if the user wants to skip an entry? We can allow each entry to be left empty on input, and note that User Interface has the responsibility to provide a default value in place of any empty input. For now, we'll skip this part of the implementation, and leave it for an exercise.

What if the user wants to look at a record that has already been stored on a file? He or she can use the application that updates records. We don't have to provide this capability here.

How about entering a series of records for a litter of puppies? Let's also put this off to the update application. Thus, the scenario is that the user enters the first puppy with our present application. Then, he or she runs the update application on that record, changing the dog's information as necessary for each puppy, and saving each edited record to a new file.

The Owner class is made up of a Name object and a Phone object.  Its only responsibilities are to create itself and return a name and a phone number when requested. We'll make this class immutable, and we provide a single constructor that has an argument for each attribute

 

Class Name: Owner

Responsibilities

Collaborations

Create(name, phone)

Get name

Get phone

Name, Phone

 

 

 Class Attributes: Now that we have classes and responsibilities, it is time to consider the internal representation for their attributes. Dog has three members, and we've already said that Name and Owner will be objects. What about Gender?

               Gender is most naturally represented by an enum type with the values FEMALE and MALE. It does not have to be a user-defined class. Within the Owner class, we can use the existing Name and Phone classes from our utility package.

Responsibility Algorithms for User Interface: The User Interface class has one responsibility: to input the data for a dog record and return a Dog object.  We use two helper methods for this process: getName and getPhone, which both use JOptionPane.  Method getName can be used to input both the dog¹s name and the owner¹s name.  To avoid confusion, a prompt is written just before the call to getName. 

inputDog() returns Dog

Set dog to new Dog()

Prompt for dog¹s name

dog.setName(getName)

Get gender using input dialog

dog.setGender(gender)

Prompt for owner¹s name

dog.setOwner(getName, getPhone)

return dog

 

get Name() returns Name

Prompt for and get first name

Prompt for and get middle name

Prompt for and get last name

return new Name(first, last, middle)

 

get Phone

Prompt for and get area code

Prompt for and get 7-digit number

  return new Phone(area code, number)

 

Responsibility Algorithms for Dog: We are now ready to develop the algorithms for the responsibilities of the Dog class. We start with the constructor, then the basic observers, the transformers, and lastly we provide the algorithms for file output and input.

Dog()

Set name to new Name("", "", "")

Get gender to FEMALE

Set owner to new Owner(new Name("", "", ""), new Phone(0, 0))

Get Name returns Name

return name

Set Name(Name newName)

Set name to newName

 

As the remaining ³get² and ³set² algorithms are identical in form, there is no need to show them.

 

Using object output, we need to apply the proper method to the entire Dog instance.  How can we refer to the entire instance from with it?  Java provides a keyword this that refers to the entire instance.  We use this keyword to write out the entire instance.  (We have more to say about this in Chapter 9.)

writeToFile(ObjectOutputStream out)

Write This to out

 

Using object input, we must create an instance into which to read the object, and then move the fields into the instance to which the method is applied.

readFromFile(ObjectInnputStream in)

Read object contents from file into Dog object inDog

Copy fields from inDog to this object's fields

 

Responsibility Algorithms for Owner:. The responsibilities for the Owner class include a constructor that sets the fields from its parameters and two observers.  These algorithms need no decomposition.

 

 

Responsibility Algorithms for Driver: The application needs a driver that calls the inputDog responsibility of the User Interface, prints the dog information on System.out,  outputs the dog object to a file.  For now, we'll use the dog's name as the name of the file, with the extension .dog. In the future, we may need to change this naming convention if we find that we have more than one dog with the same name.

main

set dog to UserInterface.inputDog()

print dog information

set filename to dog's first name + dog's last name + ".dog"

set outfile to new ObjectOutputStream(newFileOutputStream(filename))

write dog on outfile

 

Is that it? Just five lines? Yes, that's the beauty of object-oriented design. We've so effectively encapsulated all of the objects that the algorithm for the driver can be written succinctly, with very abstract statements that match our high-level view of what the application does.  And although the statements are abstract, their implementations need no further decomposition.

The UML diagram for this application reveals how much of its work is done by its collaborating classes.

 


Figure 7.17  UML Diagram for the CreateDogRecord Application

 


Implementation: Now we are ready to code these classes. We begin with the implementation of Owner, then Dog, and UserInterface, and finally the driver for the application, which we call CreateDogRecord. The UserInterface class is specific to this application, but Dog and Owner are classes that we want to import into other applications. Let's bundle them into a package called dogInfo.

We have one more task before we can complete the application.  Because we are writing object files, we must add the phrase implements Serializable to classes Dog and Owner.  Classes Name and Phone already have this phrase added.

In order to save space in our listings, one statement observers and transformers are written in a condensed form on one line.

//**************************************************************

// This class provides an object to represent a dog owner. The

// constructor takes a Name, Address, and Phone. Three observer

// methods return these attribute values

//**************************************************************

package dogInfo;

import utility.*;

public class Owner implements Serializable

{

  Name name;

  Phone phone;

  public Owner(Name newName, Phone newPhone)

  {

    name = newName;

    phone = newPhone;

  }

  public Name getName() {return name;}

  public Phone getPhone() { return phone;}

)

 

//**************************************************************

// This mutable class provides an object to represent a dog. The

// constructor takes no parameters and returns an object with

// default values for all attributes. Transformer and observer

// methods are provided for each attribute. In addition, file

// output and input methods are provided

//**************************************************************

package dogInfo;

import utility.*;

import java.util.*;

public class Dog implements Serializable

{

  public enum Gender {FEMALE, MALE};

  Name name;                      // Dog's name

  Gender gender;                  // Dog's gender

  Owner owner;                    // Dog's owner(name, address, phone)

  // Default constructor. Creates a Dog with all default values.

  public Dog()

  {

    name = new Name("", "", "");

    gender = FEMALE;

    owner = new Owner(new Name("", "", ""),

                      new Phone(0, 0));

  }

  // Basic observers that return each member of a Dog

  public Name getName() {return name;}

  public Gender getGender() {return gender;}

  public Owner getOwner() {return owner;}

  // Basic transformers that replace each member of Dog with a new value

  public void setName(Name newName) { name = newName;}

  public void setGender(Gender newGender) {gender = newGender;}

  public void setOwner(Owner newOwner) {owner = newOwner;}

  // Observer that writes contents of Dog to an object file, one member per line

  public void writeToFile(ObjectOutputStream out)

  {

    out.writeObject(this);

  }

  // Transformer that replaces contents of Dog with values read from an object file

  public void readFromFile(ObjectInputStream in) throws ClassNotFoundException

  {

    Dog inDog  = new Dog();

    inDog =  in.readObject();

    name = inDog.name;

    gender = inDog.gender;

    owner = inDog.owner;

  }

}

 

//**************************************************************

// This class provides a method that inputs all of the

// information necessary to construct and return a new Dog object

//**************************************************************

import utility.*;

import dogInfo.*;

import java.io.*;

public class UserInterface

{

  public static Dog inputDog() throws NumberFormatException

  {

    // Variable Declarations

    Dog dog = new Dog();

    Name dogName, ownerName;

    Gender gender;

    Phone phone;

    Owner owner;

    // Get dog's name, gender, and owner

    JOptionPane.showMessageDialog(null, "Enter dog's name");

    dogName = inputName();

    gender = Gender.valueOf(JOptionPane.showInputDialog

                           ("Enter gender: FEMALE or MALE"));

    JOptionPane.showMessageDialog(null, "Enter owner's name");

    ownerName = inputName();

    phone = inputPhone();

    owner = new Owner(ownerName, phone);

    dog.setName(dogName);

    dog.setGender(gender);

    dog.setOwner(owner);

    return dog;

  }

  private static Name inputName()

  {

    // Declarations

    String first, middle, last;

    // Input name

    first = JOptionPane.showInputDialog("Enter first name:");

    middle = JOptionPane.showInputDialog("Enter middle name:");

    last = JOptionPane.showInputDialog("Enter last name:");

    return new Name(first, last, middle);

  }

  private static Phone inputPhone() throws NumberFormatException

  {

    // Declarations

    int area, digits;

    // Input date

    area =

      Integer.parseInt(JOptionPane.showInputDialog("Enter area code:"));

    digits = Integer.parseInt(JOptionPane.showInputDialog

                             ("Enter number: "));

    return new Phone(area, digits);

  }

 

 

//**************************************************************

// This class contains the driver for an application that

// inputs information for a new dog record and creates a file

// containing that information, using the dog's name to generate

// the file name

//**************************************************************

import javax.swing.*;

import utility.*;

import dogInfo.*;

import java.io.*;

public class CreateDogRecord

{

  public static void main(String[] args) throws IOException,

    ClassNotFoundException

  {

    Dog dog;

    Name name;  // Dog name

    Owner owner;

    ObjectOutputStream outFile;

    ObjectInputStream inFile;

    // Call helper method to input a dog object

 

    dog = UserInterface.inputDog();

    // Access fields of dog record to print

    name = dog.getName();

    System.out.println("Dog: " + name.getFirstName() + " " +

                        name.getLastName());

    System.out.println("Gender: " + dog.getGender().toString());

    owner = dog.getOwner();

    System.out.println("Owner: " + owner.getName().getFirstName()

                        + " " + owner.getName().getLastName());

    System.out.println("Phone: " + owner.getPhone().toString());

    String fileName = dog.getName().getFirstName() +

      dog.getName().getLastName() + ".dog";

    outFile = new ObjectOutputStream(new FileOutputStream(fileName));

    dog.writeToFile(outFile);

    outFile.close();

  }

}

 

Here is the output from a test run of the application, on file PongeeAdogslife.txt:

 

Pongee

Connemara

Adogslife

MALE

David

Abernathy

Elderwine

888-411-0000

 

Testing: We designed Dog and Owner to be reusable in other applications, and put them in a package so that they can be easily imported. Whenever you develop a class for more general use than the application at hand, you cannot assume that the application fully tests the class. In this case, we can see that every method of Owner is used, and almost every method of Dog is called. However, the application does not make use of the readObjectFile   method. Thus, to ensure that Dog has been fully tested, we should develop a driver that uses the read method to input a file that has been output by the CreateDogRecord application. You are asked to do this in the exercises.

As we have noted, this application doesn't work when the user skips the input of a value. It also fails to catch entry of an invalid gender, or invalid phone numbers. A well-designed test plan would check for these cases and reveal them. Before the application is given to the customer, all of these deficiencies must be addressed.