Relationships between Classes
4.2— Composition
3. The response logic associated with the given player There are three distinct types of response logic that are possible Each player is associated with one of the three possible types of response logic More than one player may have the
same type of response logic. At random times, the same or one of the other two types of response logic replaces a player's response logic.
It is the response logic associated with a player that determines how the die outcome and relative position of the other players affects the given player's score and move. The details related to the three types of response logic are given below.
Response Type 1
If a player's move is based on response type 1, its change in position is computed as: die throw + (position of leading player – player's position) / 2
If the die throw is 3, 4, 5, or 6 the change in position is positive; otherwise it is negative. Response Type 2
If a player's move is based on response type 2, its change in position is computed as:
3 times the die throw if the dice throw is an even number; otherwise the die throw if the die throw is an odd number
The change in position is always positive. Response Type 3
If a player's move is based on response type 3, its change in position is computed as: die throw + (player's position – position of trailing player) / 2
A player is associated with a particular response type for a random number of moves uniformly distributed between 2 and 5 moves. When the lifetime of a response associated with a given player has expired, a new response type among the three is chosen with an equal likelihood of each. At the same time, a new response life is chosen (between 2 and 5 moves).
As the race between players progresses and after each player's move, a line of output should be sent to the console that specifies the player number and its current position. When the game is over, a final line of output should be written to the console that indicates the winner of the game.
4.3.2—
Analysis and Design
From the specifications we identify six classes. These are listed below with a brief description of the responsibilities of each class.
Domain Classes
Game – Owns and controls the four players and random number generator and is responsible for managing their overall play.
Player – Holds a particular response type for a limited lifetime. Each player is responsible for knowledge of its own position.
Response – An abstract class that computes the change in position for a player based on the positions of the other players and the dice throw.
Response Type 1, 2, 3. Concrete response classes that compute the change in position for a player based on rules given in Section 4.3.1.
We discuss more details for each of the classes before attempting to establish the relationships they have to each other. Class Game
Class Game owns and is responsible for creating all the Player objects. Since the Game also requires a random number object, it is responsible for creating this object. Therefore, class Game has a strong aggregation relationship with one or more players and a Random number object (i.e., strong aggregation with respect to classes Player and Random).
Game is responsible for assigning a new Response object to a player whenever its response life is zero. This action, as well as the control of the game, is established in a play method that controls the flow of the game.
Class Player
A player must know its response life, its position, and its player number. These become scalar attributes of the class. A player is always associated with a Response object passed to it by the Game. Since it does not own the Response
object, class Player has a reference relationship with respect to class Response.
A player is responsible for updating its response life whenever it is assigned a new Response object. To do this requires the use of a random number. Therefore
class Player has a reference relationship with class Random. An instance of Random is passed to Player in its constructor and held as a field. This ensures that only one random number object is created and used throughout the application.
One of the important methods of Player is makeMove. This method determines the new position of the Player. Abstract Class Response
Abstract class Response holds a reference to an array of Player objects. This array is passed to a Response object when it is created. A Response object must also hold the number of the player associated with it as well as the number of players in the array.
The abstract method changeInPosition must be implemented by each of the three concrete Response subclasses according to the rules given in the game specification.
Based on the descriptions given above, the UML diagram in Figure 4.2 depicts the relationship among the six classes that define the architecture of this application.
Explanation of Figure 4.2
The associations with a dark diamond attached to Game and an arrow attached to Random and Player indicate strong aggregation relationships with classes Player and Random. The arrows indicate that Game has access to Random and
Player but Random and Player do not have access to Game. A Game object (the whole) is responsible for creating
Player objects and a Random object, the parts.
Figure 4.2.
Class Player is shown as having a reference relationship with Response and Random, each with multiplicity 1. Class
Response is shown as having a reference relationship with class Player of multiplicity 1 or more. This implies that each concrete Response object has knowledge of the Player objects. This information is provided at initialization time through a constructor.
The three concrete Response type classes are shown as subclasses of Response. Each has its own changeInPosition
method. The fields and methods of each class are also shown in Figure 4.2. 4.3.3—
Implementation
Abstract class Response is presented in Listing 4.2.
Listing 4.2 Abstract Class Response
/**
* Abstract class that models change in position */
public abstract class Response {
// Fields
protected Player [] players; // The players in the game protected int numberPlayers; // Number of players in the game protected int playerNumber; // Player associated with Response // Constructor
public Response (Player [] players, int numberPlayers, int playerNumber) { this.players = players; this.numberPlayers = numberPlayers; this.playerNumber = playerNumber; } // Queries /**
* Returns the player's change in position based on move */
public abstract int changeInPosition(int dieThrow);
}
Instances of class Response are not allowed since it is an abstract class. The abstract method changeInPosition is-*+9 defined in each of the concrete subclasses. All concrete subclasses of Response inherit knowledge of the array of players, the number of players, and the particular player number associated with the Response object through the fields defined in class Response. Listings 4.3, 4.4, and 4.5 present the three concrete subclasses of Response.
Listing 4.3 Class TypeOneResponse
public class TypeOneResponse extends Response {
// Constructor
public TypeOneResponse (Player [] players, int numberPlayers,
int playerNumber) {
super (players, numberPlayers, playerNumber); }
// Queries
public int changeInPosition(int dieThrow) {
// Compute highest position
int highest = players[1].position(); for (int i = 2; i <= numberPlayers; i++) if (players[i].position() > highest) highest = players[i].position(); int change = dieThrow + (highest -
players[playerNumber].position()) / 2; return (dieThrow > 2) ? change : -change;
} }
In the query changeInPosition of class TypeOneResponse, the highest position is assumed to be associated with player 1. The loop that follows replaces the highest position with the position of any of the other players if their position is greater than the current highest position. Natural indexing is used in the array of players; therefore the upper limit on the loop is
numberPlayers, an attribute of abstract class Response that is inherited.
The last line of code in query changeInPosition returns a positive change in position if the die throw is greater than 2; otherwise it returns a negative change in position as given by the problem specifications.
Listing 4.4 Class TypeTwoResponse
public class TypeTwoResponse extends Response {
// Constructor
public TypeTwoResponse (Player [] players, int numberPlayers,
int playerNumber) {
super (players, numberPlayers, playerNumber); }
// Queries
public int changeInPosition(int dieThrow) {
return (dieThrow % 2 == 0) ? 3 * dieThrow : dieThrow; }
The change in position returned by changeInPosition in class TypeTwoResponse is dependent only on the die throw and is not dependent on the position of the other players.
Listing 4.5 Class TypeThreeResponse
public class TypeThreeResponse extends Response {
// Constructor
public TypeThreeResponse (Player [] players, int numberPlayers,
int playerNumber) {
super (players, numberPlayers, playerNumber); }
// Queries
public int changeInPosition(int dieThrow) {
// Compute lowest position
int lowest = players[1].position(); for (int i = 2; i <= numberPlayers; i++) if (players[i].position() < lowest) lowest = players[i].position(); int change = dieThrow +
(players(playerNumber].position() - lowest) / 2; return (dieThrow > 2) ? -change : change;
} }
The logic of method changeInPosition in class TypeThreeResponse is similar to class TypeOneResponse. Here the lowest position is computed. A negative change in position is returned if the die throw exceeds 2; otherwise a positive change in position is returned as required in the specifications.
Listings 4.3, 4.4, and 4.5 demonstrate that each of the concrete Response classes provides a specific definition of the query changeInPosition. Listing 4.6 presents the details of class Player.
Listing 4.6 Class Player
/**
* Models each player */
public class Player {
// Fields
private int playerNumber; private Response response; private int responseLife; private java.util.Random rnd; // Constructor
public Player (int number, java.util.Random rnd) {
this.rnd = rnd;
playerNumber = number; }
// Commands
public void assignResponse (Response response) {
this.response = response;
responseLife = 2 + rnd.nextInt (4); }
public void makeMove (int dice) {
responseLife--;
position += response.changeInPosition(dice); }
// Queries
public int responseLife () {
return responseLife; }
public int position () {
return position; }
public boolean wins() {
return position >= 500; }
public int playerNumber() {
return playerNumber; }
}
The makeMove command decrements the attribute responseLife and then assigns a change in position based on the query
changeInPosition sent to the Response object associated with the Player. This is a form of delegation in which the responsibility for computing a new position is transferred from a Player object to the Response object associated with the Player.
Listing 4.7 Class Game
/**
* Game class that holds the players and initiates action */
public class Game {
// Fields
private Player [] players = new Player[5]; // Use natural indexing private java.util.Random rnd = new java.util.Random(); private int numberPlayers;
// Constructor
public Game (int numberPlayers) {
this.numberPlayers = numberPlayers; // Warm up random number generator for (int i = 0; i < 50000; i++) rnd.nextDouble();
// Create four Player objects players[1] = new Player(1, rnd); players[2] = new Player(2, rnd); players[3] = new Player(3, rnd); players[4] = new Player(4, rnd);
// Assign random Response type to each player
for (int playerNumber = 1; playerNumber <= numberPlayers; playerNumber++)
assignRandomResponse(players[playerNumber]); }
// Command
public void assignRandomResponse (Player player) {
// Assign a random Response object to specified player switch (1 + rnd.nextInt(3)) {
case 1: // Player gets TypeOneResponse
player.assignResponse(new TypeOneResponse(players, numberPlayers, player.playerNumber())); break;
case 2: // Player gets TypeTwoResponse
player.assignResponse(new TypeTwoResponse(players, numberPlayers, player.playerNumber())); break;
case 3: // Player gets TypeThreeResponse
player.assignResponse(new TypeThreeResponse(players, numberPlayers, player.playerNumber())); break; } }
TEAM
FLY
Team-Fly
®public void play () { int playerNumber = 0; do { playerNumber++; if (playerNumber > numberPlayers) playerNumber = 1; if (players[playerNumber].responseLife() == 0) assignRandomResponse(players[playerNumber]); players[playerNumber].makeMove(1 + rnd.nextInt(6)); System.out.println (''Player " + playerNumber + " position: " + players[playerNumber].position()); } while (!players[playerNumber].wins());
System.out.println("\n\nPlayer " + playerNumber + " wins the game." );
}
public static void main(String[] args) {
Game game = new Game(4); game.play();
} }
Function main creates a new Game object passing the number of players as a parameter. The constructor in class Game
warms up a random number generator, creates the four players, and assigns a random response object to each player. Recall that class Player has a reference relationship to class Random. The play command is sent to the Game object. A
do-while loop controls the game and terminates when one of the players returns the value true to the query wins. Within the loop, playerNumber is incremented and is reset to 1 when its value exceeds the number of players. If the response life of a player is 0, it is assigned a new Response. The die value is computed and passed as a parameter to the
makeMove command. The play command manages the overall control of the game. A do -while loop in this method controls the process. The loop continues until a player returns true to the query wins().
4.4— Summary
• Inheritance, as the name implies, involves the transmittal of behavioral characteristics from parent class to child class. Through inheritance one can establish behavior in a base class that is available and directly usable in a hierarchy of descendent classes that extend the base class.
• A child class may extend a parent class by introducing one or more fields or methods not found in the parent or by redefining one or more parent class methods.
• Through inheritance a strong dependency and association is established between parent class and child class. Strong dependencies need to be carefully justified.
• The composition relationship is a whole/part relationship. One class representing the whole defines fields that represent the parts.
• A strong aggregation relationship implies that the class representing the whole owns and is responsible for creating each of its aggregate parts. The aggregate parts cannot be shared among other objects.
• A weak aggregation or reference relationship implies that the class representing the whole shares its aggregate parts possibly with other objects. These aggregate part objects have a life and identity of their own.
• A UML class diagram shows the relationships among the classes that define a system. The fields as well as methods of each class are depicted for each class.
• Inheritance is shown on a UML diagram with a broad-headed arrow going from the subclass to its parent.
• Strong aggregation is shown on a UML diagram with a diamond attached to the whole and a line connecting to the part class.
• A reference relationship is shown on a UML diagram with a line connecting the whole to the part class.
4.5—