CarrCh03v2 - C H A P T E R 3 Designing Classes CONTENTS...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: C H A P T E R 3 Designing Classes CONTENTS Encapsulation Specifying Methods Java Interfaces Writing an Interface Implementing an Interface An Interface as a Data Type Type Casts Within an Interface Implementation Extending an Interface Named Constants Within an Interface Interfaces Versus Abstract Classes Choosing Classes Identifying Classes CRC Cards Reusing Classes PREREQUISITES Chapter 1 Chapter 2 Appendix D Java Classes Creating Classes from Other Classes Documentation and Programming Style OBJECTIVES After studying this chapter, you should be able to G G G G Describe encapsulation, information hiding, and data abstraction Write specifications for methods that include preconditions and postconditions Write a Java interface for a class Choose appropriate classes and methods during the design of a program, including classes that might be written already 57 58 CHAPTER 3 Designing Classes O bject-oriented programming embodies three design concepts: encapsulation, inheritance, and polymorphism. We have already discussed inheritance and polymorphism. Now, building on our earlier discussion of classes, this chapter introduces encapsulation as a way to hide the details of an implementation during the design of a class. We go on to emphasize the importance of specifying how a method should behave before you implement it and of expressing your specifications as comments in your program. We introduce Java interfaces as a way to separate the declarations of a class’s behavior from its implementation. Finally, we present, at an elementary level, some techniques for identifying the classes necessary for a particular solution. Encapsulation 3.1 What is the most useful description of an automobile, if you want to learn to drive one? It clearly is not a description of how its engine goes through a cycle of taking in air and gasoline, igniting the gasoline/air mixture, and expelling exhaust. Such details are unnecessary when you want to learn to drive. In fact, such details can get in your way. If you want to learn to drive an automobile, the most useful description of an automobile has such features as the following: G G G G If you press your foot on the accelerator pedal, the automobile will move faster. If you press your foot on the brake pedal, the automobile will slow down and eventually stop. If you turn the steering wheel to the right, the automobile will turn to the right. If you turn the steering wheel to the left, the automobile will turn to the left. Just as you need not tell somebody who wants to drive a car how the engine works, you need not tell somebody who uses a piece of software all the fine details of its Java implementation. Likewise, suppose that you create a software component for another programmer to use in a program. You should describe the component in a way that tells the other programmer how to use it but that spares the programmer all the details of how you wrote the software. 3.2 Encapsulation is one of the design principles of object-oriented programming. “Encapsulation” sounds as though it means putting things into a capsule, and that image is indeed correct. Encapsulation hides the fine detail of what is inside the “capsule.” For this reason, encapsulation is often called information hiding. But part of what is in the capsule is visible. In an automobile, certain things are visible—like the pedals and steering wheel—and others are hidden under the hood. The automobile is encapsulated so that the details are hidden, and only the controls needed to drive the automobile are visible, as Figure 3-1 shows. Similarly, you should encapsulate your Java code so that details are hidden and only the necessary controls are visible. Encapsulation encloses data and methods into a class and hides the implementation details that are not necessary for using the class. If a class is well designed, its use does not require an understanding of its implementation. A programmer can use the class’s methods without knowing the details of how they are coded. The programmer needs to know only how to provide a method with appropriate arguments and can leave it up to the method to perform the right action. In this way, the programmer is spared having to worry about the internal details of the class definition. The programmer who uses encapsulated software to write more software has a simpler task. As a result, software is produced more quickly and with fewer errors. Note: Encapsulation is a design principle of object-oriented programming that encloses data and methods into a class, thereby hiding the details of a class’s implementation. A programmer Encapsulation 59 receives only enough information to be able to use the class. A well-designed class can be used as though the body of every method was hidden from view. Figure 3-1 An automobile’s controls are visible to the driver, but its inner workings are hidden 3.3 Abstraction is a process that asks you to focus on what instead of how. When you design a class, you practice data abstraction. You focus on what you want to do with or to the data without worrying about how you will accomplish these tasks and how you will represent the data. Abstraction asks you to focus on what data and operations are important. When you abstract something, you identify the central ideas. For example, an abstract of a book is a brief description of the book, as opposed to the entire book. When designing a class, you should not think about any method’s implementation. That is, you should not worry about how the class’s methods will accomplish their goals. This separation of specification from implementation allows you to concentrate on fewer details, thereby making your task easier and less error-prone. Detailed, well-planned specifications facilitate an implementation that is more likely to be successful. Note: The process of abstraction asks you to focus on what instead of how. 3.4 When done correctly, encapsulation divides a class definition into two parts, which we will call the client interface and the implementation. The client interface describes everything a programmer needs to know to use the class. It consists of the signatures for the public methods of the class, the comments that tell a programmer how to use these public methods, and any publicly defined constants of the class. The client interface part of the class definition should be all you need to know to use the class in your program. The implementation consists of all data fields and the definitions of all methods, including those that are public, private, and protected. Although you need the implementation to run a client (a program that uses the class), you should not need to know anything about the implementation to write the client. Figure 3-2 illustrates an encapsulated implementation of a class and the client interface. Although the implementation is hidden from the client, the interface is visible and provides the client well-regulated communication with the implementation. 60 CHAPTER 3 Figure 3-2 Designing Classes An interface provides well-regulated communication between a hidden implementation and a client Client Interface Signatures of public methods Public named constants Implementation Private data fields Private constants Private methods Protected methods Public methods The client interface and implementation are not separated in the definition of a Java class. They are mixed together. You can, however, create a separate Java interface as a companion to your class. A Java interface contains the signatures for a class’s public methods and can define public named constants. A later section of this chapter describes how to write and use a Java interface. We will write a number of Java interfaces in the rest of the book. Question 1 How does a client interface differ from a class implementation? Question 2 Think of an example, other than an automobile, that illustrates encapsulation. What part of your example corresponds to a client interface and what part to an implementation? Specifying Methods 3.5 Separating the purpose of a class and its methods from their implementations is vital to a successful software project. You should specify what each class and method does without concern for its implementation. Writing descriptions enables you to capture your ideas initially and to develop them so that they are clear enough to implement. Your written descriptions should reach the point where they are useful as comments in your program. You need to go beyond a view that sees comments as something you add after you write the program to satisfy an instructor or boss. Let’s focus on comments that you write for a class’s methods. Although organizations tend to have their own style for comments, the developers of Java have specified a commenting style that you should follow. If you include comments written in this style in your program, you can run a utility program called javadoc to produce documents that describe your classes. This documentation tells people what they need to know to use your class but omits all the implementation details, including the bodies of all method definitions. Specifying Methods 61 The program javadoc extracts the heading for your class, the signatures for all public methods, and comments that are written in a certain form. Each such comment must appear immediately before a public class definition or the signature of a public method and must begin with /** and end with */. Certain tags that begin with the symbol @ appear within the comments to identify various aspects of the method. For example, you use @param to identify a parameter, @return to identify a return value, and @throws to indicate an exception that the method throws. You will see some examples of these tags within the comments in this chapter. Appendix D provides the details for writing comments acceptable to javadoc. Rather than talk further about the rules for javadoc here, we want to discuss some important aspects of specifying a method. First, you need to write a concise statement of the method’s purpose or task. Beginning this statement with a verb will help you to avoid many extra words that you really do not need. In thinking about a method’s purpose, you need to consider its input parameters, if any, and describe them. You also need to describe the method’s results. Does it return a value, does it cause some action, or does it affect the state of an argument? In writing such descriptions, you should keep in mind the following ideas. 3.6 A precondition is a statement of the conditions that must be true before a method begins execution. The method should not be used, and cannot be expected to perform correctly, unless the precondition is satisfied. A precondition can be related to the description of a method’s parameters. For example, a method that computes the square root of x can have x ≥ 0 as a precondition. A postcondition is a statement of what is true after a method completes its execution. For a valued method, the postcondition will describe the value returned by the method. For a void method, the postcondition will describe actions taken and any changes to the calling object. In general, the postcondition describes all the effects produced by a method invocation. Thinking in terms of a postcondition can help you to clarify a method’s purpose. Notice that going from precondition to postcondition leaves out the how—that is, we separate the method’s specification from its implementation. Programming Tip: When a method cannot satisfy its postcondition, even though its precondition is met, it can throw an exception. (See Appendix B for a discussion of exceptions.) 3.7 Responsibility. A precondition defines responsibility. If the client is responsible for ensuring that certain conditions are met before calling the method, the method need not check the conditions. On the other hand, if the method is responsible for enforcing the conditions, the client does not check them. A clear statement of who must check a given set of conditions increases the probability that someone will do so and avoids duplication of effort. For example, you could specify the square root method that we mentioned in Segment 3.6 by writing the following comments before its signature: /** Task: Computes the square root of a number. * @param x a real number >= 0 * @return the square root of x */ In this case, the method assumes that the client will provide a nonnegative number as an argument. On the other hand, the method could assume responsibility for checking the argument. In that case, its comments could read as follows: /** Task: Computes the square root of a number. * @param x a real number 62 CHAPTER 3 Designing Classes * @return the square root of x if x >= 0 * @throws ArithmeticException if x < 0 */ Although we’ve integrated the precondition and postcondition into the previous comments, we could identify them separately, as we did for the task. Programming Tip: Specify each public method fully in comments placed before the method’s signature. State whether a method or its client is responsible for ensuring that the necessary conditions are met for the successful execution of the method. In this way, checking is done but not duplicated. During debugging, however, a method should always check that its precondition has been met. 3.8 Assertions. Preconditions and postconditions are examples of assertions. An assertion is a statement of truth about some aspect of your program’s logic. You can think of it as a boolean expression that is true, or that at least should be true, at a certain point. If an assertion is false, something is wrong with your program. You can state assertions that are not obvious as comments within your code. For example, if at some point in a method’s definition, you know that the variable sum should be positive, you could write the following comment: // Assertion: sum > 0 Such comments point out aspects of the logic that might not be clear. Additionally, they provide places for you to check the accuracy of your code during debugging. Programming Tip: Assertions written as comments within a method’s body identify aspects of your logic that you can check during debugging. 3.9 When you use inheritance and polymorphism to override a method in a base class, the method in the derived class could be inconsistent with the method in the base class. Preconditions and postconditions will help you to avoid this problem. A postcondition must apply to all versions of a method throughout the subclasses. An overridden method can add to a postcondition—that is, it can do more—but it should not do less. However, an overridden method cannot augment its precondition. In other words, it cannot require more than a version of the method in a base class requires. Question 3 Assume that you have a class Circle that has a data field radius and the following method to compute the circle’s area: public double area(); What precondition and postcondition can you write for this method? Question 4 Suppose that you have an array of positive integers. The following statements find the largest integer in the array. What assertion can you write as a comment after the if statement in the following loop? int max = 0; for (int index = 0; index < array.length; index++) { if (array[index] > max) max = array[index]; // Assertion: } 63 Java Interfaces Java Interfaces 3.10 Earlier in this chapter we spoke in general terms about the client interface, which tells you all you need to know to use a particular class in your program. An interface consists of the class’s public constants and the signatures for its public methods. Although a Java class intermixes its interface with its implementation, you can write a separate interface. A Java interface is a program component that contains public constants, signatures for public methods, and, ideally, comments that describe them. You would write a Java interface in addition to writing the class, meaning that the method signatures appear in two separate files. When you write a class that conforms to an interface, we say that the class implements the interface. A class that implements an interface must define a body for every method that the interface specifies. Java provides some interfaces for you. Others you will write yourself. For example, Java provides an interface called Comparable. Any class that implements this Comparable interface must implement the following method: public int compareTo(Object otherObject) Briefly, this method compares two objects and returns an integer that signals the result of the comparison. For example, Java’s class String implements the Comparable interface and so has a method compareTo that compares two strings. Note: The method compareTo compares two objects and returns a signed integer that indicates the result of the comparison. For example, if x and y are two instances of the same class that implements the interface Comparable, x.compareTo(y) returns G G G A negative integer if x is less than y Zero if x equals y A positive integer if x is greater than y If x and y have different types, x.compareTo(y) throws the exception ClassCastException. Writing an Interface 3.11 A Java interface begins like a class definition, except that you use the word is, an interface begins with the statement class. That public interface interface instead of interface-name rather than public class class-name The interface can contain any number of public method signatures, each followed by a semicolon. For example, Java’s Comparable interface is simply public interface Comparable { public int compareTo(Object otherObject); } // end Comparable Our example has only one method signature. Note that an interface does not declare the constructors for a class. Also note that methods within an interface are public by default, so you can omit public from their signatures. 64 CHAPTER 3 Designing Classes You store an interface definition in a file whose name is the name of the interface followed by For example, the interface Comparable is in the file Comparable.java. .java. Note: An interface can declare data fields, but they must be public. By convention, a class’s data fields are private, so any data fields in an interface should represent named constants. Thus, they should be public and final. Note: A Java interface is a good place to provide comments that specify each method’s purpose, parameters, precondition, and postcondition. In this way, you can specify a class in one file and implement it in another. 3.12 Example. Recall the class Name that we presented in Segment 1.16 of Chapter 1. The following statements define a Java interface for this class. We have included comments for only the first two methods to save space: public interface NameInterface { /** Task: Sets the first and last names. * @param firstName a string that is the desired first name * @param lastName a string that is the desired last name */ public void setName(String firstName, String lastName); /** Task: Gets the full name. * @return a string containing the first and last names */ public String getName(); public void setFirst(String firstName); public String getFirst(); public void setLast(String lastName); public String getLast(); public void giveLastNameTo(NameInterface child); public String toString(); } // end NameInterface This interface provides a client with a handy summary of the methods’ specifications. The client should be able to use the class that implements NameInterface without looking at the class. Notice that the parameter of the method giveLastNameTo has NameInterface as its data type instead of Name, as it did in Chapter 1. You should write an interface independently of any class that will implement it. We will talk about interfaces as data types beginning with Segment 3.15. Programming Tip: Naming an interface Interface names, particularly those that are standard in Java, often end in “able,” such as Comparable or Cloneable. That ending does not always provide a good name, so endings such as “er” or “Interface” are also used. Just as Java’s exception names end in “Exception,” we will usually end our interface names with “Interface.” Java Interfaces 65 Implementing an Interface 3.13 Any class that implements an interface must state so at the beginning of its definition by using an implements clause. For example, if a class C implements the Comparable interface, it would begin as follows: public class C implements Comparable The class then must provide a definition for each method declared in the interface. In this example, the class C must implement the method compareTo. The implementation of the class Name given in Chapter 1 could implement NameInterface as given in the previous segment. You would simply add an implements clause to the first line of the class definition, so that it reads as follows: public class Name implements NameInterface The class Name must now implement every method declared in NameInterface. Using an interface is a way to guarantee that a class has defined certain methods. Figure 3-3 illustrates the three files that contain NameInterface, Name, and their client. Figure 3-3 The files for an interface, a class that implements the interface, and the client The interface public interface NameInterface { . . . public class Name implements NameInterface { . . . The client The class public class Client { . . . NameInterface joe; . . . joe = new Name(); } } } NameInterface.java 3.14 Name.java Client.java Several classes can implement the same interface, perhaps in different ways. For example, many classes implement the Comparable interface and provide their own version of the compareTo method. Additionally, a class can implement more than one interface. If it does, you simply list all the interface names, separated by commas. If the class is derived from another class, the implements clause always follows the extends clause. Thus, you could write public class C extends B implements Comparable, AnotherInterface As Segment 2.19 mentioned, you cannot derive a class from more than one base class. A Java interface serves a function similar to a base class, even though it is not a class. By allowing a class to implement any number of interfaces, Java approximates multiple base classes without the complications they cause. Question 5 Write a Java interface for the class Student given in Segment 2.2 of Chapter 2. Question 6 What revision(s) should you make to the class Student so that it implements the interface you wrote for the previous question? 66 CHAPTER 3 Designing Classes An Interface as a Data Type 3.15 You can use a Java interface as you would a data type when you declare a variable, a data field, or a method’s parameter. For example, a method could have a parameter whose type is Comparable: public void myMethod(Comparable entry) Any argument that you pass to this method must be an object of a class that implements the Compainterface. Thus, by using Comparable as the parameter’s type, you ensure that the method’s argument will be able to invoke the method compareTo. In general, a method can be sure that its parameter can invoke particular methods, namely those declared in an interface, if its data type is the interface. What if a class D does not begin with the phrase implements Comparable, yet still implements the method compareTo? You could not pass an instance of D to myMethod. rable Note: By using an interface as an object’s type, you ensure that the object will have a certain set of methods. 3.16 A variable declaration such as NameInterface myName; makes myName a reference variable. Now myName can reference any object of any class that implements NameInterface. So if you have myName = new Name("Coco", "Puffs"); then myName.getFirst() returns a reference to the string implements NameInterface, and you later write "Coco". If the class AnotherName also myName = new AnotherName("April", "MacIntosh"); then myName.getFirst() returns a reference to the string "April". Chapter 2 introduced polymorphic variables when discussing inheritance. In that discussion, you saw that you could write A item = new B(); if the class B is derived from the class A. The variable item is polymorphic, since its dynamic type can differ from its static type. Here you see that the variable myName also is polymorphic. Thus, polymorphic variables can occur as a result of using either inheritance or interfaces. Type Casts Within an Interface Implementation 3.17 3.18 Implementing an interface can involve a complication. For example, notice that in the Comparable interface, the type of compareTo’s parameter is Object. We want the parameter to have the same type as the calling object, but the type of the calling object is unknown until we define a class that implements the interface. Since we want the interface to be general, we have to settle for making the parameter of type Object. Thus, in any class that implements the interface, a type cast probably will be needed within the body of the definition of the method. Example. The following class represents data about a pet and implements the interface ComparaLook for the type cast in the definition of compareTo. ble. public class Pet implements Comparable { Java Interfaces private String name; private int age; private double weight; 67 // in years // in pounds /** Task: Compares the weight of two pets. */ public int compareTo(Object other) { Pet otherPet = (Pet)other; return weight - otherPet.weight; } // end compareTo < Other methods are here. > } // end Pet Since other’s weight. data type is Object, we need to cast it to Pet to be able to access the data field We might use the class Pet in a program by writing the following statements. Note that type casting is not necessary here: Pet dog = new Pet("Fido", 5, 55.6); Pet cat = new Pet("Fluffy", 6, 10.3); if (dog.compareTo(cat) < 0) System.out.println("Dog weighs less than cat."); Question 7 Revise the class Name given in Segment 1.16 of Chapter 1 so that it implements the interface Comparable. Extending an Interface 3.19 Once you have an interface, you can derive another interface from it by using inheritance. In fact, you can derive an interface from several interfaces, even though you cannot derive a class from several classes. When an interface extends another interface, it has all the methods of the inherited interface. Thus, you can create an interface that consists of the methods in an existing interface plus some new methods. For example, working with our pet theme, suppose that we have the following interface: public interface Nameable { public void setName(String petName); public String getName(); } // end Nameable We can extend Nameable to create the interface Callable: public interface Callable extends Nameable { public void come(String petName); } // end Callable A class that implements Callable must implement the methods come, setName, and getName. 68 CHAPTER 3 3.20 Designing Classes You also can combine several interfaces into a new interface and add even more methods if you like. For example, suppose that in addition to the previous two interfaces, we define the following interfaces: public interface Capable { public void hear(); public void respond(); } // end Capable public interface Trainable extends Callable, Capable { public void sit(); public void speak(); public void lieDown(); } // end Trainable A class that implements Trainable must implement the methods and respond, as well as the methods sit, speak, and lieDown. setName, getName, come, hear, Note: A Java interface can be derived from several interfaces, even though you cannot derive a class from several classes. Question 8 Suppose that the class Pet in Segment 3.18 contains the method setName, yet does not implement the interface Nameable of Segment 3.19. Could you pass an instance of Pet as the argument of the method with the following signature? public void enterShow(Nameable pet) Named Constants Within an Interface 3.21 An interface can contain named constants, that is, public data fields that you initialize and declare as final. If you want to implement several classes that share a common set of named constants, you can define the constants in an interface that the classes implement. In this way, the classes do not have their own set of constants. You save a bit of memory, but more importantly, you have only one set of constants to keep current. 3.22 Example. For example, imagine classes for various geometric forms like circles, spheres, and cylinders. Each of these forms has a radius, and each involves the constant π. We could define the following interface that our classes would implement: public interface Circular { public static final double PI = 3.14159; public void setRadius(double newRadius); public double getRadius(); } // end Circular Although an interface could contain constants exclusively, this interface recognizes that a radius will exist, and so declares both set and get methods for it. Java Interfaces 69 Now a class Circle that implements this interface could appear as follows: public class Circle implements Circular { private double radius; public void setRadius(double newRadius) { radius = newRadius; } // end setRadius public double getRadius() { return radius; } // end getRadius public double getArea() { return PI*radius*radius; } // end getArea } // end Circle The class implements the methods setRadius and getRadius and uses the constant PI in the method getArea. The class does not define its own copy of PI but does declare radius as a data field. An interface cannot contain an ordinary data field like radius, since it is private. Interfaces Versus Abstract Classes 3.23 The purpose of an interface is similar to that of an abstract base class. However, an interface is not a base class. In fact, it is not a class of any kind. When should you use an interface and when should you use an abstract class? Use an abstract base class if you want to provide a method definition or declare a private data field that your classes will have in common. Otherwise, use an interface. Remember that a class can implement several interfaces but can extend only one abstract class. 3.24 Example. Consider the example from the previous section that involved π and a radius. Instead of defining an interface, let’s define an abstract class: public abstract class CircleBase { public static final double PI = 3.14159; private double radius; public void setRadius(double newRadius) { radius = newRadius; } // end setRadius public double getRadius() { return radius; } // end getRadius public abstract double getArea(); } // end CircleBase 70 CHAPTER 3 Designing Classes This class defines the constant PI just as the interface did in the previous section. But here we also declare the data field radius that descendant classes will inherit. Since the data field is private, the class CircleBase must implement set and get methods so that its descendant classes can access radius. If CircleBase simply declared setRadius and getRadius as abstract—omitting their implementations—a descendant class would be unable to implement them. If the definition of CircleBase stopped here, it would not be abstract, but it still would be a useful base class. However, this class also declares the abstract method getArea, which its descendant classes must implement in their own way. The following class is derived from the base class CircleBase. It implements the abstract method getArea. In doing so, it invokes the inherited method getRadius to access the inherited data field radius. Circle cannot reference the data field radius by name. public class Circle extends CircleBase { public double getArea() { double radius = getRadius(); return PI*radius*radius; } // end getArea } // end Circle In this method, radius is simply a local variable. Choosing Classes We have talked about specifying classes and implementing classes, but up to now, we have chosen the class to specify or implement. If you must design an application from scratch, how will you determine the classes you need? In this section, we introduce you to some techniques that software designers use in choosing and designing classes. Although we will mention these techniques in subsequent chapters from time to time, our intent is simply to expose you to these ideas. Future courses will develop ways to select and design classes. 3.25 Imagine that we are designing a registration system for your school. Where should we begin? A useful way to start would be to look at the system from a functional point of view, as follows: G G G Who or what will use the system? A human user or a software component that interacts with the system is called an actor. So a first step is to list the possible actors. For a registration system, two of the actors could be a student and the registrar. What can each actor do with the system? A scenario is a description of the interaction between an actor and the system. For example, a student can add a course. This basic scenario has variations that give rise to other scenarios. For instance, what happens when the student attempts to add a course that is closed? Our second step, therefore, is to identify scenarios. One way to do this is to complete the question that begins “What happens when...”. Which scenarios involve common goals? For example, the two scenarios we just described are related to the common goal of adding a course. A collection of such related scenarios is called a use case. Our third step, then, is to identify the use cases. You can get an overall picture of the use cases involved in a system you are designing by drawing a use case diagram. Figure 3-4 is a use case diagram for our simple registration system. Each actor—the student and the registrar—appears as a stick figure. The box represents the registration Choosing Classes 71 system, and the ovals within the box are the use cases. A line joins an actor and a use case if an interaction exists between the two. Figure 3-4 A use case diagram for a registration system Registration system Apply for admission Enroll student Add a course Drop a course Registrar Student Display course schedule Change address Some use cases in this example involve one actor, and some involve both. For example, only the student applies for admission, and only the registrar enrolls a student. However, both the student and the registrar can add a course to a student’s schedule. Note: Use cases depict a system from the actors’ points of view. They do not necessarily suggest classes within the system. Identifying Classes 3.26 Although drawing a use case diagram is a step in the right direction, it does not identify the classes that are needed for your system. Several techniques are possible, and you will probably need to use more than one. One simple technique is to describe the system and then identify the nouns and verbs in the description. The nouns can suggest classes, and the verbs can suggest appropriate methods within the classes. Given the imprecision of natural language, this technique is not foolproof, but it can be useful. For example, we could write a sequence of steps to describe each use case in Figure 3-4. Figure 3-5 gives a description of the use case for adding a course from the point of view of a student. Notice the alternative actions taken in Steps 2a and 4a when the system does not recognize the student or when a requested course is closed. What classes does this description suggest? Looking at the nouns, we could decide to have classes to represent a student, a course, a list of all courses offered, and a student’s schedule of courses. The verbs suggest actions that include determining whether a student is eligible to register, determining whether a course is closed, and adding a course to a student’s schedule. One way to assign these actions to classes is to use CRC cards, which we describe next. 72 CHAPTER 3 Figure 3-5 Designing Classes A description of a use case for adding a course System: Registration Use case: Add a course Actor: Student Steps: 1. Student enters identifying data. 2. System confirms eligibility to register. a. If ineligible to register, ask student to enter identification data again. 3. Student chooses a particular section of a course from a list of course offerings. 4. System confirms availability of the course. a. If course is closed, allow student to return to Step 3 or quit. 5. System adds course to student’s schedule. 6. System displays student’s revised schedule of courses. CRC Cards 3.27 Figure 3-6 A simple technique to explore the purpose of a class uses index cards. Each card represents one class. You begin by choosing a descriptive name for a class and writing it at the top of a card. You then list the names of the public methods that represent the class’s responsibilities. You do this for each class in the system. Finally, you indicate the interactions, or collaborations, among the classes. That is, you write on each class’s card the names of other classes that have some sort of interaction with the class. Because of their content, these cards are called class-responsibility-collaboration, or CRC, cards. For example, Figure 3-6 shows a CRC card for the class CourseSchedule that represents the courses in which a student has enrolled. Notice that the small size of each card forces you to write brief notes. The number of responsibilities must be small, which suggests that you think at a high level and consider small classes. The size of the cards also lets you arrange them on a table and move them around easily while you search for collaborations. A class-responsibility-collaboration (CRC) card CourseSchedule Responsibilities Add a course Remove a course Check for time conflict List course schedule Collaborations Course Student Question 9 3.28 Write a CRC card for the class Student given in Segment 2.2 of Chapter 2. The Unified Modeling Language. The use case diagram in Figure 3-4 is part of a larger notation known as the Unified Modeling Language, or UML. Designers use the UML to illustrate a software system’s necessary classes and their relationships. The UML gives people an overall view of a Choosing Classes 73 complex system more effectively than either a natural language or a programming language can. English, for example, can be ambiguous, and Java code provides too much detail. Providing a clear picture of the interactions among classes is one of the strengths of the UML. Besides the use case diagram, the UML provides a class diagram that places each class description in a box analogous to a CRC card. The box contains a class’s name, its attributes (data fields), and operations (methods). For example, Figure 3-7 shows a box for the class CourseSchedule. Typically, you omit from the box such common operations as constructors, get methods, and set methods. Figure 3-7 A class representation that can be a part of a class diagram CourseSchedule courseCount courseList addCourse(course) removeCourse(course) isTimeConflict() listSchedule() Question 10 How would the class class diagram of the UML? 3.29 Figure 3-8 Name, given in Segment 1.16 of Chapter 1, appear in a In a class diagram, lines join the boxes to show the relationships among the classes, including any inheritance hierarchy. For example, the class diagram in Figure 3-8 shows that the classes UndergradStudent and GradStudent are each derived from the class Student. An arrow with a hollow head points to the base class. Within the UML, the base class Student is said to be a generalization of UndergradStudent and GradStudent. UML notation for a base class Student and two derived classes Student UndergradStudent GradStudent 74 CHAPTER 3 Designing Classes An association is a line that represents a relationship between instances of two classes. Basically, an association represents what a CRC card calls a collaboration. For example, relationships exist among the classes Student, CourseSchedule, and Course. Figure 3-9 shows how the UML pictures these relationships. The association (line) between the classes CourseSchedule and Course, for example, indicates a relationship between objects of the class CourseSchedule and objects of the class Course. This association has an arrow pointing toward Course. The arrow indicates responsibilities. Thus, a CourseSchedule object should be able to tell us the courses it contains, but a Course object need not be able to tell us to which schedules it belongs. The UML calls this aspect of the notation the navigability. This particular arrow is said to be unidirectional, since it points in one direction. An association with arrowheads on both ends is called bidirectional. For example, a Student object can determine its course schedule, and a CourseSchedule object can determine to which student it belongs. You can assume that the navigability of an association without arrowheads is unspecified at the present stage of the design. At the ends of the association are numbers. At the end of the line beginning at CourseSchedule and extending to Course, you see the notation 0..10. This notation indicates that each CourseSchedule object is associated with between zero and ten courses. If you follow the line in the other direction, you encounter an asterisk. It has the same meaning as the notation 0..infinity. Each Course object can be associated with many, many course schedules—or with none at all. The figure also indicates a relationship between one Student object and one CourseSchedule object. This notation on the ends of an association is called the association’s cardinality or multiplicity. Figure 3-9 Part of a UML class diagram with associations Course CourseSchedule Student 1 1 * 0..10 Question 11 Combine Figures 3-8 and 3-9 into one class diagram. Then add a class that represents all courses offered this semester. What new association(s) do you need to add? AllCourses Reusing Classes 3.30 When you first start to write programs, you can easily get the impression that each program is designed and written from scratch. On the contrary, most software is created by combining already existing components with new components. This approach saves time and money. In addition, the existing components have been used many times and so are better tested and more reliable. For example, a highway simulation program might include a new highway object to model a new highway design, but it would probably model automobiles by using an automobile class that had already been designed for some other program. As you identify the classes that you need for Reusing Classes 75 your project, you should see whether any of the classes exist already. Can you use them as is, or would they serve as a good base class for a new class? 3.31 C HAPTER S UMMARY As you design new classes, you should take steps to ensure that they are easily reusable in the future. You must specify exactly how objects of that class interact with other objects. This is the principle of encapsulation that we discussed in the first section of this chapter. But encapsulation is not the only principle you must follow. You must also design your class so that the objects are general and not tailored too much for one particular program. For example, if your program requires that all simulated automobiles move only forward, you should still include a reverse in your automobile class. Some other simulation may require automobiles to back up. Admittedly, you cannot foresee all the future uses of your class. But you can and should avoid dependencies that will restrict its use later by another programmer. Chapter 14 describes the design of a class with its future use in mind. Using the principles that this chapter discusses to design a reusable class with an interface that has comments suitable for javadoc takes work. Hacking together a solution to your specific problem would take less time. But the payback for your effort will come later on, when you or another programmer needs to reuse an interface or a class. If you planned for the future when you wrote those components, every use of them will be faster and easier. Actual software developers use these principles to save time over the long term, because saving time saves them money. You should use them, too. Encapsulation is a design principle of object-oriented programming that encloses data and methods into a class, thereby hiding the details of a class’s implementation. Only enough information to allow a programmer to use the class is given. The programmer should be able to use a well-designed class as if the body of every method is hidden from view. G Abstraction is a process that asks you to focus on what instead of how. G The method compareTo compares two objects and returns a negative integer, zero, or a positive integer according to whether the comparison is less than, equal to, or greater than. G An interface declares the public methods that a class must implement and can contain public named constants. G A class that implements an interface has an implements clause at the start of the class definition. The class must implement all the methods declared in the interface. G A Java class can implement any number of interfaces. This feature gives Java an approximation to multiple inheritance, a concept that Java does not support. G Use cases depict a system from the point of view of one or more actors. Use cases do not necessarily suggest classes within the system. G A class-responsibility-collaboration (CRC) card lists a class’s name, actions, and other classes that collaborate with it. G P ROGRAMMING T IPS G A class diagram depicts the relationships among classes and, for each class, lists its name, data fields, and methods. The notation used is a part of the Unified Modeling Language (UML). G Specify each public method fully in comments placed before the method’s signature. State whether a method or its client is responsible for ensuring that the necessary conditions are met for the successful execution of the method. In this way, checking is done but not duplicated. However, during debugging, a method should always check that its precondition has been met. 76 CHAPTER 3 Designing Classes G G Assertions written as comments within a method’s body identify aspects of your logic that you can check during debugging. G E XERCISES A method that cannot satisfy its postcondition, even though its precondition is met, can throw an exception. Interface names, particularly those that are standard in Java, often end in “able,” such as Comparable or Cloneable. That ending does not always provide a good name, so endings such as “er” or “Interface” are also used. Just as Java’s exception names end in “Exception,” we will usually end our interface names with “Interface.” 1. Consider the class Circle, as given in Segment 3.22. a. Is the client or the method setRadius responsible for ensuring that the circle’s radius is positive? b. Write a precondition and a postcondition for the method setRadius. c. Write comments for the method setRadius in a style suitable for javadoc. d. Revise the method setRadius and its precondition and postcondition to change the responsibility mentioned in your answer to Part a. 2. Revise the class Circle as given in Segment 3.22, so that it also implements the interface Comparable. Implement the method compareTo so that circles are compared according to their radii. 3. Repeat Exercise 2, but instead use the class Circle as given in Segment 3.24. 4. Consider the interface NameInterface defined in Segment 3.12. We provided comments for only two of the methods. Write comments in javadoc style for each of the other methods. 5. Write a Java interface for the class CollegeStudent given in Segment 2.8 of Chapter 2. 6. What revision(s) should you make to the class CollegeStudent so that it implements the interface that you wrote for the previous exercise? 7. Revise the classes Student and CollegeStudent so that each class implements the interface Comparable and, therefore, implements the method compareTo. P ROJECTS 1. Design a class Fraction of fractions. Each fraction is signed and has a numerator and a denominator that are integers. Your class should be able to add, subtract, multiply, and divide two fractions. These methods should have a fraction as a parameter and should return the result of the operation as a fraction. The class should also be able to find the reciprocal of a fraction, compare two fractions, determine whether two fractions are equal, and convert a fraction to a string. Your class should handle zero denominators. Reusing Classes 77 Fractions should always occur in lowest terms, and the class should be responsible for this requirement. For example, if the user tries to create a fraction such as 4/8, the class should set the fraction to 1/2. Likewise, the results of all arithmetic operations should be in lowest terms. Note that a fraction can be improper—that is, have a numerator that is larger than its denominator. Such a fraction, however, should be in lowest terms. Begin by writing a CRC card for this class. Then write a Java interface that declares each public method. Include javadoc-style comments to specify each method. 2. Write a Java class Fraction that implements both the interface you designed in Project 1 and the Comparable interface. Begin with reasonable constructors. Design and implement useful private methods, and include comments that specify them. To reduce a fraction such as 4/8 to lowest terms, you need to divide both the numerator and the denominator by their greatest common denominator. The greatest common denominator of 4 and 8 is 4, so when you divide the numerator and denominator of 4/8 by 4, you get the fraction 1/2. The following recursive algorithm determines the greatest common denominator of two positive integers: Algorithm gcd(integerOne, integerTwo) if (integerOne % integerTwo == 0) result = integerTwo else result = gcd(integerTwo, integerOne % integerTwo) return result It will be easier to determine the correct sign of a fraction if you force the fraction’s denominator to be positive. However, your implementation must handle negative denominators that the client might provide. Write a program that adequately demonstrates your class. 3. A mixed number contains both an integer portion and a fractional portion. Design a class MixedNumber of mixed numbers that uses the class Fraction that you designed in Project 1. Provide operations for MixedNumber that are analogous to those of Fraction. That is, provide operations to set, retrieve, add, subtract, multiply, and divide mixed numbers. The fractional portion of any mixed number should be in lowest terms and have a numerator that is strictly less than its denominator. Write a Java interface, including javadoc comments, for this class. 4. Implement the class MixedNumber that you designed in Project 3. Use the operations in Fraction whenever possible. For example, to add two mixed numbers, convert them to fractions, add the fractions by using Fraction’s add operation, and then convert the resulting fraction to mixed form. Use analogous techniques for the other arithmetic operations. Handling the sign of a mixed number can be a messy problem if you are not careful. Mathematically, it makes sense for the sign of the integer part to match the sign of the fraction. But if you have a negative fraction, for example, the toString method for the mixed number could give you the string "-5 -1/2", instead of "-5 1/2", which is what you would normally expect. Here is a possible solution that will greatly simplify computations. Represent the sign of a mixed number with a character data field. Once this sign is set, make the integer and fractional parts positive. When a mixed number is created, if the given integer part is not zero, take the sign of the integer part as the sign of the mixed 78 CHAPTER 3 Designing Classes number and ignore the signs of the fraction’s numerator and denominator. However, if the given integer part is zero, take the sign of the given fraction as the sign of the mixed number. 5. Consider two identical pails. One pail hangs from a hook on the ceiling and contains a blue liquid. The other pail is empty and rests on the floor directly below the first pail. Suddenly a small hole develops in the bottom of the full pail. Blue liquid streams from the full pail and falls into the empty pail on the floor, as Figure 3-10 illustrates. Liquid continues to fall until the upper pail is empty. Notice that the outline of the pails is black; only the liquid is blue. Design classes for a program that illustrates this action. When the program begins execution, it should display both pails in their original condition before the leak occurs. Decide whether the leak will occur spontaneously or at a user signal, such as pressing the Return key or clicking the mouse. If the latter, you could have the user position the cursor on the pail bottom to indicate where the leak will occur. Write CRC cards and Java interfaces that include comments in javadoc style. 6. Implement your design for the leaking pail, as described in Project 5. Figure 3-10 A leaking pail (Project 5) ...
View Full Document

This note was uploaded on 04/29/2010 for the course CS 5503 taught by Professor Kaylor during the Spring '10 term at University of West Alabama-Livingston.

Ask a homework question - tutors are online