You have learned that Java is an object-oriented language. You have also seen a number of reasons to use object-oriented languages. For example, objects provide a natural mechanism for modeling many problems and solutions. In addition, well-designed objects provide a natural mechanism for supporting reuse. But how does one do good object design? There is no real substitute for practice and experience: you learn object design by designing the objects for programs and then reflecting (and re-reflecting) on your designs. However, there are a number of processes by which beginning designers can develop the basic skills. We will investigate a few of those in this laboratory session.
The processes we will investigate include narratives, question and answer sessions, drawing diagrams, and generalization through related examples. You will find that it is often best to do design in dialog with others, as one person may find both flaws and additional possibilities in the design of another person.
Since design skills come from experience, it is important that you record your design decisions when you make them. Then, as you work on your project, you should reflect on those decisions and continue to record observations. If you have made a bad decision, it helps to have the implications documented so that you can avoid making similarly inappropriate decisions in the future. If you have made a good decision, it helps to have it documented so that you can reuse the ideas that led to that decision.
These strategies are certainly not the only ones you can use to improve your design skills. For example, you may find it helpful to study the design of larger systems. You might read about the design of such a system or even attempt to replicate that design.
As an example, we will consider the game of Othello. In particular, we will think about how we might design a program that allows people to play Othello. If you do not know Othello, do not worry; here is a description:
Othello is a two-person game played on an eight-by-eight board. It is often convenient to number the rows of the board 1-8 and the columns A-H. The game begins with two black pieces and two white pieces on the center four squares of the board (D4, D5, E4, and E5). There is a black piece in the top-left and bottom-right of those four squares (D4 and E5). There is a white piece in the top-right and bottom-left of those four squares (D5 and E4). Players alternate turns, with each player making a legal move when it is his or her turn. A legal move consists of placing a piece so that two of pieces surround a row, column, or diagonal of the opponent's pieces. All of the opponent's pieces are then flipped (from black to white or from white to black). If a player cannot make a legal move, that player loses his or her turn. Play continues until there are no longer blank squares on the board or neither player can make a legal move. The player with the most pieces is the winner.
Note that this is, in effect, a short narrative describing the game. Such narrative descriptions are good first steps in object design. As with all pieces of prose, this narrative included both nouns and verbs. It is often the case that nouns correspond closely to the objects and classes in a program and that verbs correspond closely to the methods these objects and classes provide. If you circle the nouns, you are on your way to determining which objects and classes belong in your program. If you box the verbs, you are also on your way to determining the methods.
For example, you might note that the description uses the nouns
player, piece, and move. This makes it
likely that a program that simulates an Othello game will include the
classes Player
, Piece
, and Move
.
What methods will these classes provide? Now we can turn our attention
to the verbs. We see that players make moves and that pieces
flip. This suggests that the Player
class will
most likely include a makeMove
or chooseMove
methods and that the Piece
class will include a
flip
method.
Other nouns might suggest particular objects. For example, ``black''
is presumably an instance of Color
, or at least the state
of a Piece
(or both). Still other nouns may be extraneous.
For example, while the description of Othello uses the term ``opponent'',
it is unlikely that we will need an Opponent
class or even
an opponent
object in class Player
. However,
it is possible that some methods might benefit from an opponent
variable.
In Experiment O1.1 you will begin to consider the design of an Othello class.
The objects, classes, and methods one develops through narratives are often somewhat sketchy and are usually insufficient to form a whole program. We have not yet determined what attributes or fields each class needs, nor have we determined what parameters the methods will need. In addition, there are often a number of implicit objects in every description. How do we garner this additional information? We can garner information through a more careful reading of the narrative, asking questions as we go. What types of questions should we ask?
For example, closer reading of the description of Othello suggests that
someone needs to determine whose turn it is. It may be helpful to use
a Referee
class that selects the current player. It may also
be that the class that runs the game makes this determination.
Similarly, you note that there are many references to legal moves.
What determines whether a move is legal? A set of Rules
.
The Rules
also determine when the game is over and, in effect,
who moves next.
Now let us consider the makeMove
method. What information
does a player need to make a move? The player certainly needs to know
his or her color, the state of the board, and the applicable rules.
Since a player always has the same color, we might make that a field in
the Player
class. On the other hand, the state of the
board changes, so we might make that a parameter of the
makeMove
method. The rules are consistent, so we might
also make those a field in the Player
class.
Are these the only decisions we could have made? Certainly not. We
might have instead chosen to make all color, board, and rules all
parameters to the makeMove
method or all fields in the
Player
class, or some other combination. How do you
decide what is best to do? Again, experience will tell you. For
now, a good strategy is to make things that can be changed by other
objects (e.g., the board) parameters, while things that will remain
consistent throughout the game (e.g., the rules) can be fields.
You might note that we have not begun to specify how
makeMove
operates, we have only specified what it
does. At this point, it is appropriate to stay at this level of
abstraction. In part, the how will be determined by our
further steps. In part, the how may differ depending on other
issues. For example, in a text-based Othello game,
makeMove
might draw the board and prompt for the player to
type in a move; in a graphical Othello game, makeMove
might
wait for the player to click on a legal square; in a game against the
computer, the computer player will make a move by running an algorithm
that chooses an appropriate space.
Finally, we might ask ourselves what constitutes a move. Basically, it
is the placement of a piece on the board at a particular position. This
suggests that the Move
class will have at least two
attributes: a Piece
and a Position
. We also
note that moves update the board. This suggests that the
Board
class will need an update
method that
takes a Move
as a parameter.
In Experiment O1.2 you will refine your descriptions.
At this stage in your design, you should have a rough outline of the
classes you will need in the program, some of the methods those classes
will need to provide, and some of the fields each class will use. You
can use this information to start writing the code for your programs.
For example, I've determined that each Player
will need
a Color
for its pieces and a makeMove
method.
I might also note that I'll need the Color
in order to
create a particular player, giving me an idea of what the constructor
will look like. This analysis leads to the following class description.
import Color; /** * A person playing an Othello game (or, perhaps, any game). * * @author Samuel A. Rebelsky * @version 1.1 of September 1998 */ public class Player { // +--------+-------------------------------------------------- // | Fields | // +--------+ /** The color of the pieces the player places on the board. */ protected Color mycolor; // +--------------+-------------------------------------------- // | Constructors | // +--------------+ /** * Create a new player that uses a particular color of * piece. */ public Player(Color piececolor) { this.mycolor = piececolor; } // Player(Color) // +---------+------------------------------------------------- // | Methods | // +---------+ /** * Choose a move to make (but don't make the move). */ public Move makeMove(Board b) { // Code not yet available. } // makeMove(Board) } // class Player
In Experiment O1.3 you will begin to develop classes for the project.
Our descriptions up to this point have ignored an important component: interaction with the user or users. While not all programs, classes, objects, or methods will interact with the user, many will. How do we determine what role such interactions play in the design of our program? We write more detailed narratives that describe how we envision users interacting with the program.
For example, we might describe two players using our Othello game as follows.
Two players, William and Jonathan, start the game. The game prompts for the player name. William enters his name for white; Jonathan enters his name for black. The game displays the game board (with white pieces at D5 and E4 and black pieces at D4 and E5). It then prompts William for his move. William places a white piece at C4, surrounding the black piece at D4. The game automatically flips the piece at D4 and then prompts Jonathan for his move. Jonathan chooses to place a black piece at A1. The game determines that this is not a legal move, informs Jonathan, and then asks for another move.
You'll note that we used ``the game'' a great deal in this
narrative. You will often find that you use a similar noun (perhaps
``the program'') in describing interaction. Often, this will be your
controlling object, or even the the main
routine in that
object.
How do we use such narratives? Sometimes it is to identify additional
objects and classes (as in the Game
class suggested in
the previous paragraph). More frequently, it is to identify additional
classes and methods that are necessary. For example, we might note that we
need to draw boards, perhaps with a draw
method provided
by the Board
class.
Again, it helps to ask questions. For example ``How does it
display the game board?'' or ``How does it prompt for moves?''
You might also ask ``What object does the prompting?'' In this
case, it is likely that the Player
object prompts for moves
as part of its work for the makeMove
method. In order to
do this, it may need additional parameters or fields, such as a
SimpleOutput
object to write to and a
SimpleInput
object to read from.
As we progress towards a full implementation, we can turn our narrative and questions into pseudocode, as in
To play Othello: create the board, rules, and players while the rules do not indicate that the game is over if the current player has an available move then repeatedly ask the current player to select a move until the player selects a legal move make that move, updating the board switch players else // the current player has no legal moves switch players end if end while count the pieces and report the winner
Thinking of alternative situations is also helpful. For example, one might describe an interaction between one player and the computer as follows.
Michelle starts the Othello game. It asks her whether she wants to play against the computer or another player. She selects the computer as an opponent. The game then asks for her name. She enters her name, Michelle. The game tells her that it will play white and she will play black. It then displays the board. The computer moves first and places a white piece at C4.
Again, we may want to ask some questions. For example, ``How does the computer make a move?'' One method would be to check every square on the board, identify all the legal moves, and pick one using some heuristic (a simple one is ``the first legal move I find''; a more interesting one might be ``the one that flips the most pieces''; better ones may pay attention to placement of pieces and number of pieces on the board).
Comparing alternate narratives can also help identify some objects. For
example, if we read the second narrative again, we may ask ourselves
``Which object computes the computer's move?'' Since objects
in class Player
develop moves in the first scenario, it may
make sense to use related objects in the second scenario. In
particular, we might consider building a subclass of the
Player
object, such as ComputerPlayer
.
In Experiment O1.4 you will develop narratives for some of the interactions in the game. In Experiment O1.5 you will continue coding the Othello game.
As you may have guessed, most object-oriented programs are not just collections of random objects, interacting chaotically. Rather, the interactions between objects are carefully scripted. How does one script these interactions? We can develop scripts by thinking carefully about the relationships between objects, again based on our earlier narratives.
The interaction narratives give a good beginning. For example, we can tell that:
Game
object calls the
display
method of the a Board
object.
Game
object calls the
makeMove
method of a Player
object,
which returns a Move
object.
makeMove
method of some Player
objects
request a Move
from some type of interaction object
or objects.
Many programmers find it helpful to diagram these relationships, representing them visually in addition to or instead of textually. Often, it is best for each programmer to decide on the visualization that is most appropriate for him or her. Typically a diagram will include a box for each object in the program, with sub-boxes for each of the methods. A call to a method is represented by a line from calling object or method to callee. The response may be represented by a dotted line.
In Experiment O1.6 you will practice drawing diagrams.
As we saw earlier, one of the primary reasons to do object-oriented programming is that it can easily support reuse. That is, well designed objects that work in one program should also be usable in another program. Similarly, by changing just one or two objects in a program, one might make it do something somewhat different.
In our Othello game, we might decide to support other size boards (e.g., 10x10 or 6x6), more than two players, or alternate starting positions. Each change may affect multiple classes. It is our goal to minimize the number of classes affected.
For example, if we had finished coding the Othello game and later decided to support different size boards (and we were not careful about the original design of the game), it is likely that we will need to change
makeMove
method of the Player
class,
since there are a wider variety of spaces to select;
Rules
class to support the different size boards;
and
Board
class to include a constructor which takes
the board size as a parameter.
If we design the various classes in such a way that they make fewer
assumptions, these updates might be easier. For example, if we had
written the makeMove
method so that it queried the board
for its size or for a list of legal spaces, then it might not be
necessary to update makeMove
at all when we changed the
size of the board. The secret is to think about these alternatives
before coding, rather than afterwards.
As another example, consider the pseudocode for a game of Othello that we developed earlier. Could we reuse this code for other games simply by changing the board, players, and rules? No. The pseudocode includes a number of assumptions about the rules of Othello. For example, it assumes that players alternate turns (except when one player cannot make a move). If we were going to support other games, such as chess, we would need to write a new description. Hence, it might be better to rewrite the algorithm as follows:
To play a game create the game board, rules, and players while the rules do not indicate that the game is over determine the current player let that player make a move end while determine the winner
In fact, we could even make this a parameterized method
To play a game given a board, rules, and players while the rules do not indicate that the game is over determine the current player let that player make a move end while determine the winner
In Experiment O1.7 you will consider other generalizations for the design of the Othello game.
[Front Door] [Introduction] [Code]
Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.
Source text last modified Tue Oct 26 10:46:56 1999.
This page generated on Tue Oct 26 15:38:11 1999 by Siteweaver.
Contact our webmaster at rebelsky@math.grin.edu