18.5. Use Case Realizations for the Monopoly Iteration
First, an education point: Please don't dismiss this case study because it isn't a business application. The logic, especially in later iterations, becomes quite complex, with rich OO design problems to solve. The core object design principles that it illustratesapplying Information Expert, evaluating the coupling and cohesion of alternativesare relevant to object design in all domains.We are designing a simplified version of Monopoly in iteration-1 for a scenario of the use case Play Monopoly Game . It has two system operations: initialize (or startUp ) and playGame . Following our guideline, we will ignore initialization design until the last step and focus first on the main system operationsonly playGame in this case.iteration-1 requirements p. 44 Also, to support the goal of low representational gap (LRG), we look again at Figure 18.21, which shows the Domain Model. We turn to it for inspiration as we design the domain layer of the Design Model.
Figure 18.21. Iteration-1 Domain Model for Monopoly.
How to Design playGame?
The playGame system operation occurs when the human game observer performs some UI gesture (such as clicking a "play game" button) to request the game to play as a simulation while the observer watches the output.We didn't write a detailed use case or an operation contract for this case study, as most people know the rules; our focus is the design issues, not the requirements.
Choosing the Controller Class
Our first design choice involves selecting the controller for the system operation message playGame that comes from the UI layer into the domain layer. By the Controller pattern, here are some choices:
Represents the overall "system," "root object," a specialized device, or a major subsystem. | MonopolyGame a kind of root object: We think of most of the other domain objects as "contained within" the MonopolyGame . Abbreviated MGame in most of the UML sketches.MonopolyGameSystem a name suggesting the overall system |
Represents a receiver or handler of all system events of a use case scenario. | PlayMonopolyGameHandler constructed from the pattern <use-case-name> "Handler"PlayMonopolyGameSession |
Figure 18.22. Applying Controller to the
playGame system operation.The Game-Loop Algorithm
Before discussing OO design choices, we prepare by considering the basic algorithm of the simulation. First, some terminology:
- turn
a player rolling the dice and moving the piece - round
all the players taking one turn
Now the game loop:
Recall that the iteration-1 version does not have a winner, so the simulation simply runs for N rounds.
for N rounds
for each Player p
p takes a turn
Who is Responsible for Controlling the Game Loop?
Reviewing the algorithm: The first responsibility is game loop controllooping for N rounds and having a turn played for each player. This is a doing responsibility and is not a creation or controller problem, so naturally, Expert should be considered. Applying Expert means asking, "What information is needed for the responsibility?" Here's the analysis:
Information Needed | Who Has the Information? |
---|---|
the current round count | No object has it yet, but by LRG, assigning this to the MonopolyGame object is justifiable. |
all the players (so that each can be used in taking a turn) | Taking inspiration from the domain model, MonopolyGame is a good candidate. |
- It factors the play-single-round logic into a helper method; it is good to organize cohesive chunks of behavior into small separate methods.
- Good OO method design encourages small methods with a single purpose. This supports High Cohesion at the method level.
- The name playRound is inspired by domain vocabularythat's desirable, it improves comprehension.
Figure 18.23. Game loop.
Who Takes a Turn?
Taking a turn involves rolling the dice and moving a piece to the square indicated by the total of the dice face values.What object should be responsible for taking the turn of a player? This is a doing responsibility. Again, Expert applies.Now, a naive reaction might be to say "a Player object should take the turn" because in the real world a human player takes a turn. Howeverand this is a key point OO designs are not one-to-one simulations of how a real domain works, especially with respect to how people behave. If you applied the (wrong) guideline "put responsibilities in software objects as they are assigned to people" then, for example in the POS domain, a Cashier software object would do almost everything! A violation of High Cohesion and Low Coupling. Big fat objects.Rather, object designs distribute responsibilities among many objects by the principle of Information Expert (among many others).Therefore, we should not choose a Player object just because a human player takes a turn.Yet, as we shall see, Player turns out to be a good choice for taking a turn. But the justification will be by Expert, not inspiration from how humans behave. Applying Expert means asking, "What information is needed for the responsibility?" Here's the analysis:
Information Needed | Who Has the Information? |
---|---|
current location of the player (to know the starting point of a move) | Taking inspiration from the domain model, a Piece knows its Square and a Player knows its Piece . Therefore, a Player software object could know its location by LRG. |
the two Die objects (to roll them and calculate their total) | Taking inspiration from the domain model, MonopolyGame is a candidate since we think of the dice as being part of the game. |
all the squaresthe square organization (to be able to move to the correct new square) | By LRG, Board is a good candidate. |
When there are multiple partial information experts to choose from, place the responsibility in the dominant information expertthe object with the majority of the information. This tends to best support Low Coupling.Unfortunately, in this case, are all rather equal, each with about one-third of the informationno dominant expert.So, here's another guideline to try:Guideline :
When there are alternative design choices, consider the coupling and cohesion impact of each, and choose the best.OK, that can be applied. MonopolyGame is already doing some work, so giving it more work impacts its cohesion, especially when contrasted with a Player and Board object, which are not doing anything yet. But we still have a two-way tie with these objects.So, here's another guideline:Guideline :
When there is no clear winner from the alternatives other guidelines, consider probable future evolution of the software objects and the impact in terms of Information Expert, cohesion, and coupling.For example, in iteration-1, taking a turn doesn't involve much information. However, consider the complete set of game rules in a later iteration. Then, taking a turn can involve buying a property that the player lands on, if the player has enough money or if its color fits in with the player's "color strategy." What object would be expected to know a player's cash total? Answer: a Player (by LRG). What object would be expected to know a player's color strategy? Answer: a Player (by LRG, as it involves a player's current holdings of properties).Thus, in the end, by these guidelines Player turns out to be a good candidate, justified by Expert when we consider the full game rules.
My goodness, that was detailed! Surely this discussion was more detailed than you normally want to read! Yet, if you can now follow its reasoning and apply it in new situations, it will serve you very well for the remainder of your career as an OO developer, and thus have been worth the effort. |
Figure 18.24. Player takes a turn by Expert.
[View full size image]
Notice the approach to indicating that the takeTurn message is sent to each player in a collection named players .
Taking a Turn
Taking a turn means:
- calculating a random number total between 2 and 12 (the range of two dice)
- calculating the new square location
- moving the player's piece from an old location to a new square location
Who Coordinates All This?
The above three steps need to be coordinated by some object. Since the Player is responsible for taking a turn, the Player should coordinate.
The Problem of Visibility
However, that the Player coordinates these steps implies its collaboration with the Die, Board , and Piece objects. And this implies a visibility needthe Player must have an object reference to those objects.Since the Player will need visibility to the Die, Board , and Piece objects each and every turn, we can usefully initialize the Player during startup with permanent references to those objects.
The Final Design of playGame
Based on the above design decisions, the emerging dynamic design is as shown in Figure 18.25 and the static design as in Figure 18.26. Notice that each message, each allocation of responsibility, was methodically and rationally motivated by the GRASP principles. As you come to master these principles, you will be able to reason through a design and evaluate existing ones in terms of coupling, cohesion, Expert, and so forth.
Figure 18.25. Dynamic design for
playGame .[View full size image]
Figure 18.26. Static design for
playGame .235), which would be easy and appropriate in a UML tool; but for wall sketching, informality suffices.
The Command-Query Separation Principle
Notice in Figure 18.25 that the message to roll the Die is followed by a second getFaceValue to retrieve its new faceValue . In particular, the roll method is void it has no return value. For example:
Why not make roll non-void and combine these two functions so that the roll method returns the new faceValue , as follows?
// style #1; used in the official solution
public void roll()
{
faceValue = // random num generation
}
public int getFaceValue()
{
return faceValue;
}
You can find many examples of code that follow style #2, but it is considered undesirable because it violates the Command-Query Separation Principle , (CQS) a classic OO design principle for methods [Meyer88]. This principle states that every method should either be:
// style #2; why is this poor?
public int roll()
{
faceValue = // random num generation
return faceValue;
}
- a command method that performs an action (updating, coordinating, …), often has side effects such as changing the state of objects, and is void (no return value); or
- a query that returns data to the caller and has no side effectsit should not permanently change the state of any objects
Butand this is the key pointa method should not be both .The roll method is a commandit has the side effect of changing the state of the Die's faceValue . Therefore, it should not also return the new faceValue , as then the method also becomes a kind of query and violates the "must be void" rule.
Motivation: Why Bother?
CQS is widely considered desirable in computer science theory because with it, you can more easily reason about a program's state without simultaneously modifying that state. And it makes designs simpler to understand and anticipate. For example, if an application consistently follows CQS, you know that a query or getter method isn't going to modify anything and a command isn't going to return anything. Simple pattern. This often turns out to be nice to rely on, as the alternative can be a nasty surpriseviolating the Principle of Least Surprise in software development.Consider this contrived but explosive counter-example in which a query method violates CQS:
Missile m = new Missile();
// looks harmless to me!
String name = m.getName();
…
public class Missile
{
// …
public String getName()
{
launch(); // launch missile!
return name;
}
} // end of class
Initialization and the 'Start Up' Use Case
The initialize system operation occurs, at least abstractly, in a Start Up use case. For this design, we must first choose a suitable root object that will be the creator of some other objects. For example, MonopolyGame is itself a good candidate root object. By Creator, the MonopolyGame can justifiably create the Board and Players , for exampleand the Board can justifiably create the Squares , for example. We could show the details of the dynamic design with UML interaction diagrams, but I'll use this case as an opportunity to show a UML dependency line stereotyped with «create», in a class diagram. Figure 18.27 illustrates a static view diagram that suggests the creation logic. I ignore the fine details of the interactions. In fact, that's probably suitable, because from this UML sketch we (the developers who drew this) can pretty easily figure out the creation details while coding.