Section 1.3. Designing a Riddle Program


[Page 26]

1.3. Designing a Riddle Program

The first step in the program-development process is making sure you understand the problem (Fig. 1.1). Thus, we begin by developing a detailed specification, which should address three basic questions:

  • What exactly is the problem to be solved?

  • How will the program be used?

  • How should the program behave?

In the real world, the problem specification is often arrived at through an extensive discussion between the customer and the developer. In an introductory programming course, the specification is usually assigned by the instructor.

To help make these ideas a little clearer, let's design an object-oriented solution to a simple problem.

Problem Specification

Design a class that will represent a riddle with a given question and answer. The definition of this class should make it possible to store different riddles and to retrieve a riddle's question and answer independently.


1.3.1. Problem Decomposition

Most problems are too big and too complex to be tackled all at once. So, the next step in the design process is to divide the problem into parts that make the solution more manageable. In the object-oriented approach, a problem is divided into objects, where each object will handle one specific aspect of the program's overall job. In effect, each object will become an expert or specialist in some aspect of the program's overall behavior.

Divide and conquer


Note that there is some ambiguity here about how far we should go in decomposing a given program. This ambiguity is part of the design process. How much we should decompose a program before its parts become "simple to solve" depends on the problem we're trying to solve and on the problem solver.

One useful design guideline for trying to decide what objects are needed is the following:

Effective Design: Looking for Nouns

Choosing a program's objects is often a matter of looking for nouns in the problem specification.


Again, there's some ambiguity involved in this guideline. For example, the key noun in our current problem is riddle, so our solution will involve an object that serves as a model for a riddle. The main task of this Java object will be simply to represent a riddle. Two other nouns in the specification are question and answer. Fortunately, Java has built-in String objects that represent strings of characters such as words or sentences. We can use two String objects for the riddle's question and answer. Thus, for this simple problem, we need only design one new type of objecta riddlewhose primary role will be to represent a riddle's question and answer.

Don't worry too much if our design decisions seem somewhat mysterious at this stage. A good understanding of object-oriented design can come only after much design experience, but this is a good place to start.


[Page 27]

1.3.2. Object Design

Once we have divided a problem into a set of cooperating objects, designing a Java program is primarily a matter of designing and creating the objects themselves. In our example, this means we must now design the features of our riddle object. For each object, we must answer the following basic design questions:

  • What role will the object perform in the program?

  • What data or information will it need?

  • What actions will it take?

  • What interface will it present to other objects?

  • What information will it hide from other objects?

For our riddle object, the answers to these questions are shown in Figure 1.2. Bear in mind that although we talk about designing an object, we are really talking about designing the object's class. A class defines the collection of objects that belong to it. The class can be considered the object's type. This is the same as for real-world objects. Thus, Seabiscuit is a horsethat is, Seabiscuit is an object of type horse. Similarly, an individual riddle, such as the newspaper riddle, is a riddle. That is, it is an object of type Riddle.

Figure 1.2. Design specification for the Riddle class.

  • Class Name: Riddle

  • Role: To store and retrieve a question and answer

  • Attributes (Information)

    • question: A variable to store a riddle's question (private)

    • answer: A variable to store a riddle's answer (private)

  • Behaviors

    • Riddle(): A method to set a riddle's question and answer

    • getQuestion(): A method to return a riddle's question

    • getAnswer(): A method to return a riddle's answer


The following discussion shows how we arrived at the decisions for the design specifications for the Riddle class, illustrated in Figure 1.2.

The role of the Riddle object is to model an ordinary riddle. Because a riddle is defined in terms of its question and answer, our Riddle object will need some way to store these two pieces of information. As we learned in Chapter 0, an instance variable is a named memory location that belongs to an object. The fact that the memory location is named makes it easy to retrieve the data stored there by invoking the variable's name. For example, to print a riddle's question we would say something like "print question," and whatever is stored in question would be retrieved and printed.

What is the object's role?


In general, instance variables are used to store the information that an object needs to perform its role. They correspond to what we have been calling the object's attributes. Deciding on these variables provides the answer to the question "What information does the object need?"

What information will the object need?



[Page 28]

Next we decide what actions a Riddle object will take. A useful design guideline for actions of objects is the following:

Effective Design: Looking for Verbs

Choosing the behavior of an object is often a matter of looking for verbs in the problem specification.


For this problem, the key verbs are set and retrieve. As specified in Figure 1.2, each Riddle object should provide some means of setting the values of its question and answer variables and a means of retrieving each value separately.

What actions will the object take?


Each of the actions we have identified will be encapsulated in a Java method. As you will recall from Chapter 0, a method is a named section of code that can be invoked, or called upon, to perform a particular action. In the object-oriented approach, calling a method (method invocation) is the means by which interaction occurs among objects. Calling a method is like sending a message between objects. For example, when we want to get a riddle's answer, we would invoke the getAnswer() method. This is like sending the message "Give me your answer." A special method known as a constructor is invoked when an object is first created. We will use the Riddle() constructor to give specific values to the riddle's question and answer variables.

In designing an object, we must decide which methods should be made available to other objects. This determines what interface the object should present and what information it should hide from other objects. In general, those methods that will be used to communicate with an object are designated as part of the object's interface. Except for its interface, all other information maintained by each riddle should be kept "hidden" from other objects. For example, it is not necessary for other objects to know where a riddle object stores its question and answer. The fact that they are stored in variables named question and answer rather than variables named ques and ans is irrelevant to other objects.

What interface will it present, and what information will it hide?


Effective Design: Object Interface

An object's interface should consist of just those methods needed to communicate with or to use the object.


Effective Design: Information Hiding

An object should hide most of the details of its implementation.


Taken together, these various design decisions lead to the specification shown in Figure 1.3. As our discussion has illustrated, we arrived at the decisions by asking and answering the right questions. In most classes the attributes (variables) are private. This is represented by a minus sign (-). In this example, the operations (methods) are public, which is represented by the plus sign (+). The figure shows that the Riddle class has two hidden (or private) variables for storing data and three visible (or public) methods that represent the operations it can perform.

Figure 1.3. A UML class diagram representing the Riddle class.



[Page 29]

1.3.3. Data, Methods, and Algorithms

Among the details that must be worked out in designing a riddle object is deciding what type of data, methods, and algorithms we need. There are two basic questions involved:

  • What type of data will be used to represent the information needed by the riddle?

  • How will each method carry out its task?

Like other programming languages, Java supports a wide range of different types of data, some simple and some complex. Obviously a riddle's question and answer should be represented by text. As we noted earlier, Java's String type, which is designed to store text, can be considered a string of characters.

What type of data will be used?


In designing a method, you have to decide what the method will do. In order to carry out its task, a method will need certain information, which it may store in variables. In addition, it will have to carry out a sequence of individual actions to perform the task. This is called its algorithm which is a step-by-step description of the solution to a problem. And, finally, you must decide what result the method will produce. Thus, as in designing objects, it is important to ask the right questions:

  • What specific task will the method perform?

  • What information will it need to perform its task?

  • What algorithm will the method use?

  • What result will the method produce?

How will each method carry out its task?


Methods can be thought of as using an algorithm to complete a required action. The algorithm required for the Riddle() constructor is very simple but also typical of constructors for many classes. It takes two strings and assigns the first to the question instance variable and then assigns the second to the answer instance variable. The algorithms for the other two methods for the Riddle class are even simpler. They are referred to as get methods that merely return, or produce, the value that is currently stored in an instance variable.

Not all methods are so simple to design, and not all algorithms are so simple. Even when programming a simple arithmetic problem, the steps involved in the algorithm will not always be as obvious as they are when doing the calculation by hand. For example, suppose the problem were to calculate the sum of a list of numbers. If we were telling a classmate how to do this problem, we might just say, "Add up all the numbers and report their total." But this description is far too vague to be used in a program. By contrast, here's an algorithm that a program could use:

  1. Set the initial value of the sum to 0.

  2. If there are no more numbers to total, go to step 5.

  3. Add the next number to the sum.

  4. Go to step 2.

  5. Report the sum.

Algorithm design


Each step in this algorithm is simple and easy to follow. It would be relatively easy to translate it into Java. Because English is somewhat imprecise as an algorithmic language, programmers frequently write algorithms in the programming language itself or in pseudocode, a hybrid language that combines English and programming language structures without being too fussy about programming-language syntax. For example, the preceding algorithm might be expressed in pseudocode as follows:


[Page 30]

sum = 0 while (more numbers remain)     add next number to sum print the sum 


Pseudocode


Of course, it is unlikely that an experienced programmer would take the trouble to write out pseudocode for such a simple algorithm. But many programming problems are quite complex, and careful design is required to minimize the number of errors in the program. In such situations, pseudocode could be useful.

Another important part of designing an algorithm is to trace itthat is, to step through it line by lineon some sample data. For example, we might test the list-summing algorithm by tracing it on the list of numbers shown here.

Sum

List of Numbers

0

54 30 20

54

30 20

84

20

104


Initially, the sum starts out at 0 and the list of numbers contains 54, 30, and 20. On each iteration through the algorithm, the sum increases by the amount of the next number, and the list diminishes in size. The algorithm stops with the correct total left under the sum column. While this trace didn't turn up any errors, it is frequently possible to find flaws in an algorithm by tracing it in this way.

1.3.4. Coding into Java

Once a sufficiently detailed design has been developed, it is time to start generating Java code. The wrong way to do this would be to type the entire program and then compile and run it. This generally leads to dozens of errors that can be both demoralizing and difficult to fix.

The right way to code is to use the principle of stepwise refinement. The program is coded in small stages, and after each stage the code is compiled and tested. For example, you could write the code for a single method and test that method before moving on to another part of the program. In this way, small errors are caught before moving on to the next stage.

Stepwise refinement


The code for the Riddle class is shown in Figure 1.4. Even though we have not yet begun learning the details of the Java language, you can easily pick out the key parts in this program: the instance variables question and answer of type String, which are used to store the riddle's data; the Riddle() constructor and the getQuestion() and getAnswer() methods make up the interface. The specific language details needed to understand each of these elements will be covered in this and the following chapter.


[Page 31]

Figure 1.4. The Riddle class definition.

/*  * File: Riddle.java  * Author: Java, Java, Java  * Description: Defines a simple riddle.  */ public class Riddle extends Object            // Class header {                                             // Begin class body   private String question;                    // Instance variables   private String answer;   public Riddle(String q, String a)           // Constructor method   {     question = q;     answer = a;   } // Riddle()   public String getQuestion()                 // Instance method   {     return question;   } // getQuestion()   public String getAnswer()                   // Instance method   {     return answer;   } // getAnswer() } // Riddle class                             // End class body 

1.3.5. Syntax and Semantics

Writing Java code requires that you know its syntax and semantics. A language's syntax is the set of rules that determines whether a particular statement is correctly formulated. As an example of a syntax rule, consider the following two English statements:

The rain in Spain falls mainly on the plain.   // Valid Spain rain the mainly in on the falls plain.   // Invalid 


Syntax


The first sentence follows the rules of English syntax (grammar), and it means that it rains a lot on the Spanish plain. The second sentence does not follow English syntax, and, as a result, it is rendered meaningless. An example of a Java syntax rule is that a Java statement must end with a semicolon.

However, unlike in English, where you can still be understood even when you break a syntax rule, in a programming language the syntax rules are very strict. If you break even the slightest syntax rulefor example, if you forget just a single semicolonthe program won't work at all.

Similarly, the programmer must know the semantics of the languagethat is, the meaning of each statement. In a programming language, a statement's meaning is determined by what effect it will have on the program. For example, to set the sum to 0 in the preceding algorithm, an assignment statement is used to store the value 0 into the memory location named sum. Thus, we say that the statement

Semantics


sum = 0; 



[Page 32]

assigns 0 to the memory location sum, where it will be stored until some other part of the program needs it.

Learning Java's syntax and semantics is a major part of learning to program. This aspect of learning to program is a lot like learning a foreign language. The more quickly you become fluent in the new language (Java), the better you will be at expressing solutions to interesting programming problems. The longer you struggle with Java's rules and conventions, the more difficult it will be to talk about problems in a common language. Also, computers are a lot fussier about correct language than humans, and even the smallest syntax or semantic error can cause tremendous frustration. So try to be very precise in learning Java's syntax and semantics.

1.3.6. Testing, Debugging, and Revising

Coding, testing, and revising a program is a repetitive process, one that may require you to repeat the different program-development stages shown in Figure 1.1. According to the stepwise-refinement principle, the process of developing a program should proceed in small, incremental steps, where the solution becomes more refined at each step. However, no matter how much care you take, things can still go wrong during the coding process.

A syntax error is an error that breaks one of Java's syntax rules. Such errors will be detected by the Java compiler. Syntax errors are relatively easy to fix once you understand the error messages provided by the compiler. As long as a program contains syntax errors, the programmer must correct them and recompile the program. Once all the syntax errors are corrected, the compiler will produce an executable version of the program, which can then be run.

Syntax errors


When a program is run, the computer carries out the steps specified in the program and produces results. However, just because a program runs does not mean that its actions and results are correct. A running program can contain semantic errors, also called logic errors. A semantic error is caused by an error in the logical design of the program causing it to behave incorrectly, producing incorrect results.

Semantic errors


Unlike syntax errors, semantic errors cannot be detected automatically. For example, suppose that a program contains the following statement for calculating the area of a rectangle:

return length + width; 


Adding length and width instead of multiplying them will give an incorrect area calculation. Because there is nothing syntactically wrong with the expression length + width, however, the compiler won't detect an error in this statement. Thus, the computer will execute this statement and compute the incorrect area.

Semantic errors can only be discovered by testing the program, and they are sometimes very hard to detect. Just because a program appears to run correctly on one test doesn't guarantee that it contains no semantic errors. It might only mean that it has not been adequately tested.

Fixing semantic errors is known as debugging a program, and when subtle errors occur this can be the most frustrating part of the whole program-development process. The examples presented in this text will occasionally provide hints and suggestions on how to track down bugs, or errors, in your code. One point to remember when you are trying to find a very subtle bug is that no matter how convinced you are that your code is correct and the bug was caused by an error in the computer, the error will almost certainly turn out to be caused by your code!


[Page 33]

1.3.7. Writing Readable Programs

Becoming a proficient programmer goes beyond simply writing a program that produces correct output. It also involves developing good programming style, which includes how readable and understandable your code is. Our goal is to help you develop a programming style that satisfies the following principles:

  • Readability. Programs should be easy to read and understand. Comments should be used to document and explain the program's code.

  • Clarity. Programs should employ well-known constructs and standard conventions and should avoid programming tricks and unnecessarily obscure or complex code.

  • Flexibility. Programs should be designed and written so that they are easy to modify.

Programming style





Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net