The key to developing software is to apply the concept of abstraction. You will learn many levels of abstraction from this book. Method abstraction is achieved by separating the use of a method from its implementation. The client can use a method without knowing how it is implemented. The details of the implementation are encapsulated in the method and hidden from the client who invokes the method. This is known as information hiding or encapsulation . If you decide to change the implementation, the client program will not be affected, provided that you do not change the method signature. The implementation of the method is hidden from the client in a "black box," as shown in Figure 5.11.
You have already used the System.out.print method to display a string, the JOptionPane.showInputDialog method to read a string from a dialog box, and the max method to find the maximum number. You know how to write the code to invoke these methods in your program, but as a user of these methods, you are not required to know how they are implemented.
The concept of method abstraction can be applied to the process of developing programs. When writing a large program, you can use the "divide and conquer" strategy, also known as stepwise refinement , to decompose it into subproblems. The subproblems can be further decomposed into smaller, more manageable problems.
Suppose you write a program that displays the calendar for a given month of the year. The program prompts the user to enter the year and the month, and then displays the entire calendar for the month, as shown in Figure 5.12.
Let us use this example to demonstrate the divide-and-conquer approach.
How would you get started on such a program? Would you immediately start coding? Beginning programmers often start by trying to work out the solution to every detail. Although details are important in the final program, concern for detail in the early stages may block the problem-solving process. To make problem-solving flow as smoothly as possible, this example begins by using method abstraction to isolate details from design and only later implements the details.
For this example, the problem is first broken into two subproblems: get input from the user, and print the calendar for the month. At this stage, the creator of the program should be concerned with what the subproblems will achieve, not with how to get input and print the calendar for the month. You can draw a structure chart to help visualize the decomposition of the problem (see Figure 5.13(a)).
Use the JOptionPane.showInputDialog method to display input dialog boxes that prompt the user to enter the year and the month.
The problem of printing the calendar for a given month can be broken into two subproblems: print the month title, and print the month body, as shown in Figure 5.13(b). The month title consists of three lines: month and year, a dash line, and the names of the seven days of the week. You need to get the month name (e.g., January) from the numeric month (e.g., 1). This is accomplished in getMonthName (see Figure 5.14(a)).
In order to print the month body, you need to know which day of the week is the first day of the month ( getStartDay ) and how many days the month has ( getNumberOfDaysInMonth ), as shown in Figure 5.14(b). For example, December 2005 has thirty-one days, and the first of the month is Thursday, as shown in Figure 5.12.
How would you get the start day for the first date in a month? There are several ways to find the start day. The simplest approach is to use the Calendar class in §9.3, "The Calendar and GregorianCalendar Classes." For now, an alternative approach is used. Assume that you know that the start day ( startDay1800 = 3 ) for January 1, 1800 was Wednesday. You could compute the total number of days ( totalNumberOfDays ) between January 1, 1800 and the first date of the calendar month. The start day for the calendar month is (totalNumberOfDays + startDay1800) % 7 , since every week has seven days. So the getStartDay problem can be further refined as getTotalNumberOfDays , as shown in Figure 5.15(a).
To get the total number of days, you need to know whether the year is a leap year and the number of days in each month. So getTotalNumberOfDays is further refined into two subproblems: isLeapYear and getNumberOfDaysInMonth , as shown in Figure 5.15(b). The complete structure chart is shown in Figure 5.16.
Now we turn our attention to implementation. In general, a subproblem corresponds to a method in the implementation, although some are so simple that this is unnecessary. You would need to decide which modules to implement as methods and which to combine in other methods. Decisions of this kind should be based on whether the overall program will be easier to read as a result of your choice. In this example, the subproblem readInput can be simply implemented in the main method.
You can use either a "top-down" approach or a "bottom-up" approach. The " top-down" approach implements one method in the structure chart at a time from the top to the bottom. Stubs can be used for the methods waiting to be implemented. A stub is a simple but incomplete version of a method. The use of stubs enables you to test invoking the method from a caller. Implement the main method first, and then use a stub for the printMonth method. For example, let printMonth display the year and the month in the stub. Thus, your program may begin like this:
public class PrintCalendar { /** Main method */ public static void main(String[] args) { // Prompt the user to enter year String yearString = JOptionPane.showInputDialog( "Enter full year (e.g., 2001):" ); // Convert string into integer int year = Integer.parseInt(yearString); // Prompt the user to enter month String monthString = JOptionPane.showInputDialog( "Enter month as number between 1 and 12:" ); // Convert string into integer int month = Integer.parseInt(monthString); // Print calendar for the month of the year printMonth(year, month); } /** A stub for printMonth may look like this */ public static void printMonth( int year, int month) { System.out.print(month + " " + year); } /** A stub for printMonthTitle may look like this */ public static void printMonthTitle( int year, int month) { } /** A stub for getMonthName may look like this */ public static String getMonthName( int month) { return "January" ; // a dummy value } /** A stub for getMonthNmae may look like this */ public static int getStartDay( int year, int month) { return 1 ; // a dummy value } /** A stub for getNumberOfDaysInMonth may look like this */ public static int getNumberOfDaysInMonth( int year, int month) { return 31 ; // a dummy value } /** A stub for getTotalNumberOfDays may look like this */ public static int getTotalNumberOfDays( int year, int month) { return 10000 ; // a dummy value } /** A stub for getTotalNumberOfDays may look like this */ public static boolean isLeapYear( int year) { return true ; // a dummy value } }
Compile and test the program, and fix any errors. You can now implement the printMonth method. For methods invoked from the printMonth method, you can again use stubs.
The bottom-up approach implements one method in the structure chart at a time from the bottom to the top. For each method implemented, write a test program to test it. The top-down and bottom-up approaches are both fine. Both approaches implement methods incrementally, help to isolate programming errors, and make debugging easy. Sometimes they can be used together.
The isLeapYear(int year) method can be implemented using the following code:
return (year % 400 == (year % 4 == && year % 100 != ));
Use the following facts to implement getTotalNumberOfDaysInMonth(int year, int month) :
January, March, May, July, August, October, and December have thirty-one days.
April, June, September, and November have thirty days.
February has twenty-eight days during a regular year and twenty-nine days during a leap year. A regular year, therefore, has 365 days, whereas a leap year has 366 days.
To implement getTotalNumberOfDays(int year, int month) , you need to compute the total number of days ( totalNumberOfDays ) between January 1, 1800 and the first day of the calendar month. You could find the total number of days between the year 1800 and the calendar year and then figure out the total number of days prior to the calendar month in the calendar year. The sum of these two totals is totalNumberOfDays .
To print a body, first pad some space before the start day and then print the lines for every week, as shown for December 2005 (see Figure 5.12).
The complete program is given in Listing 5.8.
1 import javax.swing.JOptionPane; 2 3 public class PrintCalendar { 4 /** Main method */ 5 public static void main(String[] args) { 6 // Prompt the user to enter year 7 String yearString = JOptionPane.showInputDialog( 8 "Enter full year (e.g., 2001):" ); 9 10 // Convert string into integer 11 int year = Integer.parseInt(yearString); 12 13 // Prompt the user to enter month 14 String monthString = JOptionPane.showInputDialog( 15 "Enter month in number between 1 and 12:" ); 16 17 // Convert string into integer 18 int month = Integer.parseInt(monthString); 19 20 // Print calendar for the month of the year 21 printMonth(year, month); 22 } 23 24 /** Print the calendar for a month in a year */ 25 static void printMonth( int year, int month) { 26 // Print the headings of the calendar 27 printMonthTitle(year, month); 28 29 // Print the body of the calendar 30 printMonthBody(year, month); 31 } 32 33 /** Print the month title, e.g., May, 1999 */ 34 static void printMonthTitle( int year, int month) { 35 System.out.println( " " + getMonthName(month) 36 + " " + year); 37 System.out.println ( " “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “ “" ); 38 System.out.println( " Sun Mon Tue Wed Thu Fri Sat" ); 39 } 40 41 /** Get the English name for the month */ 42 static String getMonthName( int month) { 43 String monthName = null ; 44 switch (month) { 45 case 1: monthName = "January" ; break ; 46 case 2: monthName = "February" ; break ; 47 case 3: monthName = "March" ; break ; 48 case 4: monthName = "April" ; break ; 49 case 5: monthName = "May" ; break ; 50 case 6: monthName = "June" ; break ; 51 case 7: monthName = "July" ; break ; 52 case 8: monthName = "August" ; break ; 53 case 9: monthName = "September" ; break ; 54 case 10: monthName = "October" ; break ; 55 case 11: monthName = "November" ; break ; 56 case 12: monthName = "December" ; 57 } 58 59 return monthName; 60 } 61 62 /** Print month body */ 63 static void printMonthBody( int year, int month) { 64 // Get start day of the week for the first date in the month 65 int startDay = getStartDay(year, month) ; 66 67 // Get number of days in the month 68 int numberOfDaysInMonth = getNumberOfDaysInMonth(year, month) ; 69 70 // Pad space before the first day of the month 71 int i = ; 72 for (i = ; i < startDay; i++) 73 System.out.print( " " ); 74 75 for (i = 1 ; i <= numberOfDaysInMonth; i++) { 76 if (i < 10 ) 77 System.out.print( " " + i); 78 else 79 System.out.print( " " + i); 80 81 if ((i + startDay) % 7 == ) 82 System.out.println(); 83 } 84 85 System.out.println(); 86 } 87 88 /** Get the start day of the first day in a month */ 89 static int getStartDay( int year, int month) { 90 // Get total number of days since 1/1/1800 91 int startDay1800 = 3 ; 92 int totalNumberOfDays = getTotalNumberOfDays(year, month); 93 94 // Return the start day 95 return (totalNumberOfDays + startDay1800) % 7 ; 96 } 97 98 /** Get the total number of days since January 1, 1800 */ 99 static int getTotalNumberOfDays( int year, int month) { 100 int total = ; 101 102 // Get the total days from 1800 to year - 1 103 for ( int i = 1800 ; i < year; i++) 104 if (isLeapYear(i)) 105 total = total + 366 ; 106 else 107 total = total + 365 ; 108 109 // Add days from January to the month prior to the calendar month 110 for ( int i = 1 ; i < month; i++) 111 total = total + getNumberOfDaysInMonth(year, i); 112 113 return total; 114 } 115 116 /** Get the number of days in a month */ 117 static int getNumberOfDaysInMonth( int year, int month) { 118 if (month == 1 month == 3 month == 5 month == 7 119 month == 8 month == 10 month == 12 ) 120 return 31 ; 121 122 if (month == 4 month == 6 month == 9 month == 11 ) 123 return 30 ; 124 125 if (month == 2 ) return isLeapYear(year) ? 29 : 28 ; 126 127 return 0 ; // If month is incorrect 128 } 129 130 /** Determine if it is a leap year */ 131 static boolean isLeapYear( int year) { 132 return year % 400 == (year % 4 == && year % 100 != ); 133 } 134 } |
The program does not validate user input. For instance, if the user enters either a month not in the range between 1 and 12 or a year before 1800 , the program would display an erroneous calendar. To avoid this error, add an if statement to check the input before printing the calendar.
This program prints calendars for a month but could easily be modified to print calendars for a whole year. Although it can only print months after January 1800, it could be modified to trace the day of a month before 1800 .
Note
Method abstraction modularizes programs in a neat, hierarchical manner. Programs written as collections of concise methods are easier to write, debug, maintain, and modify than would otherwise be the case. This writing style also promotes method reusability. |
Tip
When implementing a large program, use the top-down or bottom-up approach. Do not write the entire program at once. This approach seems to take more time for coding (because you are repeatedly compiling and running the program), but it actually saves time and makes debugging easier. |