Principle 6.5.6. EFFECTIVE DESIGN: Method Decomposition.
Methods should be designed to have a clear focus. If you find a method becoming too long, you should break its algorithm into subtasks and define a separate method for each subtask.
CheckerBoard
class. The details of its design are described in the next section.CheckerBoard
instance in its main()
method, and then adds the checkerboard to a JFrame to display it.. Checkerboard is a Canvas, so we get access to the JFrame’s Graphics
context when it is passed as an argument to the paint()
method. Recall that the main()
method is invoked when CheckerBoardProgram is run,and then the paint()
methods are invoked automatically by calling the setVisible(true)
method in main()
. Thus, the action taken by this program is simply to draw a visual representation of the checkerboard.CheckerBoard
paint()
method override’s Canvas’s paint, so it must be public and a part of the CheckerBoard
’s interface. The task of drawing a checkerboard involves two distinct subtasks: (1) drawing the board itself, which will involve drawing a square with smaller squares of alternating colors; and, (2) drawing the checkers on the checkerboard. A good design for the paint()
method would be simply to invoke helper methods that encapsulate these two subtasks. This is good method design because it results in relatively small methods, each of which performs a very well-defined task. Let’s call these methods drawBoard()
and drawCheckers()
, respectively. Their signatures are shown in Figure 6.5.3, which summarizes the design of the CheckerBoard
class.drawBoard
and drawCheckers()
methods, we must first discuss CheckerBoard
’s several instance variables. The first two variables LEFT_X
and UPPER_Y
, give the absolute position of the upper left corner of the checkerboard on the JFrame’s drawing panel. The SQ_SIDE
variable gives the size of the checkerboard’s individual squares. N_ROWS
and N_COLS
give the number of rows and columns in the checkerboard (typically, 8 by 8). All of these variables are integers. The final four variables, SQ_COLOR1
, SQ_COLOR2
, CHECKER_COLOR1
, and CHECKER_COLOR2
, specify the colors of the checkerboard and the checkers.CheckerBoard
’s instance methods, the complete definition of the CheckerBoard
class is given in Figure 6.5.4. Note how simple its paint()
method is. public void paint(Graphics g) { // Draw board and checkers
drawBoard(g);
drawCheckers(g);
}
Graphics
object. This is passed to the paint()
method when the paint()
method is invoked in the program. Because the paint()
method delegates the details of the drawing algorithm to its helper methods, drawBoard()
and drawCheckers()
, it has to pass them a reference to the Graphics
object.drawBoard()
method uses a nested for
loop to draw an \(8 \times 8\) array of rectangles of alternating colors. The loop variables, row
and col
, both range from 0 to 7. The expression used to determine alternating colors tests whether the sum of the row and column subscripts is even: \(((row + col) \% 2 == 0)\text{.}\) If their sum is even, we use one color; if odd, we use the other color.+ | 0 1 2 3 ------------ 0 | 0 1 2 3 1 | 1 2 3 4 2 | 2 3 4 5 3 | 3 4 5 6
Graphics
setColor()
method to alternate between the two colors designated for the checkerboard, SQ_COLOR1
and SQ_COLOR2
. We then use the following method call to draw the colored squares:g.fillRect(LEFT_X+col*SQ_SIDE, UPPER_Y+row*SQ_SIDE, SQ_SIDE, SQ_SIDE);
row
col
, together with the constants specifying the top left corner of the board (UPPER_Y
and LEFT_X
) and the size of the squares (SQ_SIDE
) to calculate the location and size of each square. The calculation here is illustrated in Figure 6.5.5. The first two parameters in fillRect(left,top,width,height)
specify the coordinates for the rectangle’s top-left corner. These are calculated as a function of the rectangle’s row and column position within the checkerboard and the rectangle’s width and height, which are equal for the squares of a checkerboard.drawCheckers()
method also uses a nested for
loop to trace through the checkerboard’s rows and columns. In this case, however, we draw checkers on just the dark-colored squares—that is, those that satisfy the expression \((row+col) \% 2 == 1)\)—on the first three rows of each player’s side of the board. So, each player’s checkers initially are located in the first three rows and last three rows of the checker board:if ((row + col)%2 == 1) {// One player has top 3 rows
if (row < 3){
g.setColor(CHECKER_COLOR1);
g.fillOval(LEFT_X+col*SQ_SIDE,
UPPER_Y+row*SQ_SIDE,SQ_SIDE-2,SQ_SIDE-2);
}//if
if (row >= N_ROWS - 3) { // Other has bottom 3 rows
g.setColor(CHECKER_COLOR2);
g.fillOval(LEFT_X+col*SQ_SIDE,
UPPER_Y+row*SQ_SIDE,SQ_SIDE-2,SQ_SIDE-2);
}//if
}//if
fillOval()
method to draw them. Note that the parameters for fillOval(left, top, width, height)
are identical to those for fillRect()
. The parameters specify an enclosing rectangle in which the oval is inscribed. In this case, of course, the enclosing rectangle is a square, which causes fillOval()
to draw a circle.CheckerBoard
class illustrates an important principle of method design. First, rather than placing all of the commands for drawing the checkerboard and the checkers into one method, we broke up this larger task into distinct subtasks. This resulted in small methods, each of which has a well defined purpose.