| Q: | Why does the
Game class run the sequence of steps?
Isn't that the responsibility of some “main
program?” |
| A: | Coffee Shop Answer. We haven't finished designing the entire application, so
we need to reflect our own ignorance of how the final
application will be assembled from the various parts. Rather
than allocate too many responsibilities to
Game, and possibly finding conflicts or
complication, we'd rather allocate too few responsibilities
until we know more. From another point of view, designing the main program is
premature because we haven't finished designing the
entire application. We anticipate a
Game object being invoked from some
statistical data gathering object to run one game. The data
gathering object will then get the final stake from the player
and record this. Game's responsibilities
are focused on playing the game itself. We'll need to add a
responsibility to Game to collaborate
with the data gathering class to run a number of games as a
“session”. Deeper Answer. In procedural programming (especially in languages like
COBOL), the “main program” is allocated almost
all of the responsibilities. These procedural main programs
usually contain a number of elements, all of which are very
tightly coupled. We have seen highly skilled programmers who
are able to limit the amount of work done in the main program,
even in procedural languages. In OO
languages, it becomes possible for even moderately skilled
programmers to reduce the main program to a short list of
object constructors, with the real work delegated to the
objects. We find that “main program” classes are
relatively hard to reuse, and prefer to keep them as short as
possible. |
| Q: | Why is
Outcome a separate class? Each object
that is an instance of Outcome only has
two attributes; why not use an array of Strings for the names,
and a parallel array of integers for the odds? |
| A: | Representation. We prefer not to decompose an object into separate data
elements. If we do decompose this object, we will have to ask
which class would own these two arrays? If
Wheel keeps these, then
Table becomes very tightly coupled to
these two arrays that should be Wheel's
responsibility. If Table keeps these,
then Wheel is priviledged to know
details of how Table is implemented. If
we need to change these arrays to another storage structure,
two classes would change instead of one. Having the name and odds in a single
Outcome object allows us to change the
representation of an Outcome. For
example, we might replace the String as the identification of
the outcome, with a collection of the individual numbers that
comprise this outcome. This would identify a straight bet by the
single winning number; an even money bet would be identified by
an array of the 18 winning numbers. Responsibility. he principle of isolating responsibility would be broken
by this “two parallel arrays” design because now
the Game class would need to know how
to compute odds. In more complex games, there would be the
added complication of figuring the rake. Consider a game where
the Player's strategy depends on the
potential payout. Now the Game and the
Player both have copies of the
algorithm for computing the payout. A change to one must be
paired with a change to the other. The alternative we have chosen is to encapsulate the
payout algorithm along with the relevant data items in a single
bundle. |
| Q: | If Outcome encapsulates the
function to compute the amount won, isn't it just a glorified
subroutine? |
| A: | In a limited way, yes. A class can be thought of as a
glorified subroutine library that
captures and isolates data elements along with their associated
functions. For some new designers, this is a helpful summary of
the basic principle of encapsulation. Inheritance and
subclasses, however, make a class more powerful than a simple
subroutine library with private data. Inheritance is a way to
create a family of closely-related subroutine libraries in a
simple way that is validated by the compiler. |
| Q: | What is the distinction between an
Outcome and a
Bet? |
| A: | We need to describe the propositions on the table on which
you can place bets. The propositions are distinct from an actual
amount of money wagered on a proposition. There are a lot of
terms to choose from, including bet, wager, proposition, place,
location, or outcome. We opted for using
Outcome because it seemed to express the
open-ended nature of a potential outcome, different from an
amount bet on a potential outcome. In a way, we're considering
the Outcome as an abstract possibility,
and the Bet as a concrete action taken by
a player. Also, as we expand this simulation to cover other games,
we will find that the randomized outcome is not something we can
directly bet on. In Roulette, however, all outcomes are
something we can be bet on, as well as a great many combinations
of outcomes. We will revisit this design decision as we move on
to other games. |
| Q: | Why are the classes so small? |
| A: | First-time designers of OO applications
are sometimes uncomfortable with the notion of
emergent behavior. In procedural
programming languages, the application's features are always
embodied in a few key procedures. Sometimes a single procedure,
named main. A good OO design partitions
responsibility. In many cases, this subdivision of the
application's features means that the overall behavior is not
captured in one central place. Rather, it emerges from the
interactions of a number of objects. We have found that smaller elements, with very finely
divided responsibilities, are more flexible and permit change.
If a change will only alter a portion of a large class, it can
make that portion incompatible with other portions of the same
class. A symptom of this is a bewildering nest of if-statements
to sort out the various alternatives. When the design is
decomposed down more finely, a change can be more easily
isolated to a single class. A much simpler sequence of
if-statements can be focused on selecting the proper class,
which can then simply carry out the desired functions. |