Design Discussion Notes
Thomas Kennedy
1 Tic-Tac-Toe Proper Design
1.1 Step 1: Finding the Pieces
1.1.1 Candidate Classes
- Game
- Player
- Board
- Cell
- Symbol
- Strategy
- Move
- Rules
- Referee
- Score
- Winner
- Loser
- Win
- Loss
- Stalemate
- Tie
- Turn
- Round
- Position
1.1.2 Candidate Responsibilities
- make move
- examine board
- determine ordering
- check move
- validate move
- check for a win
- check for a loss
- check for a stalemate
- check if board is full
- check cell
1.2 Step 1.5: Finding Duplicates
1.2.1 Pruning Step (Synonym Check) Candidate Classes
- Game
- Player
- Board
- Cell (Position)
- Symbol
- Strategy
- Move
- Rules
- Referee
- Score
- Winner
- Loser
- Win
- Loss
- Stalemate
- Tie
- Turn
- Round
1.2.2 Pruning Step (Synonym Check) Candidate Responsibilities
- make move
- examine board
- check cell
- determine ordering
- validate move (check move)
- check for a win
- check for a loss
- check for a stalemate
- check if board is full
1.3 Step 2: The Big Picture
This is a simple example: A Single Tic-Tac-Toe Match. I am going to look a some pseudo-code for selected pieces of a single match. Let us look at a single move by a single player:
selectedCell <- player1.makeMove()
symbol <- player1.getSymbol()
board.set(selectedCell, symbol)
Let us assume–for the moment–that all players make valid moves all the time. That leaves us with two classes:
- Player
- Action: makeMove
- Board
- Action: retrieve cell
- Action: check if empty
- Action: update cell
It would be really nice if we had some sort of UML Class Diagram to handle this notation–instead of inventing our own notation.
@startuml
Player : symbol
Player : makeMove()
Board : getCell(position)
Board : setCell(position, symbol)
@enduml
The resulting UML Class Diagram is a good start…
Of course, I need to add the missing move
argument to Player::makeMove
.
The corresponding PlantUML markup can be written as:
@startuml
hide empty members
Player : name
Player : symbol
Player : makeMove(theBoard : Board, theMove)
Board : getCell(position)
Board : setCell(position, symbol)
class Strategy {
}
Player --> Strategy: employs
@enduml
Note: I added hide empty members
to collapse all empty attributes and behaviors. I do not particularly like the resulting markup. I am a stickler for consistency. Let us tweak the Player
and Board
lines.
@startuml
hide empty members
class Player {
name
symbol
makeMove(theBoard : Board, theMove)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
Player --> Strategy: employs
@enduml
Let us add the Referee
and Game
classes to our diagram. I am pretty sure I will include both Game
and Referee
in my final model. If for some reason I am fundamentally mistaken… I can remove them later.
@startuml
hide empty members
class Player {
name
symbol
makeMove(theBoard : Board, theMove)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
}
class Game {
}
Player --> Strategy: employs
@enduml
1.4 Step 3: Back to the Lists
Let us revisit our lists of Noun Phrases
GamePlayerBoardCell (Position)
SymbolStrategyMove- Rules
Referee- Score
- Winner
- Loser
- Win
- Loss
- Stalemate
- Tie
- Turn
- Round
and Verb Phrases
make move- examine board
- check cell
- determine ordering
- validate move (check move)
- check for a win
- check for a loss
- check for a stalemate
- check if board is full
2 Tackling Game and Referee
Let us tackle Game
and Referee
concurrently. We have not tackled:
- Rules
- Score
- Winner
- Loser
- Win
- Loss
- Stalemate
- Tie
- Turn
- Round
I argue that Rules
are covered by Referee
. However, I am not sure. I better make a note to ask the Domain Expert (Question Time).
I also argue that Winner
and Loser
are not appropriately captured as classes. A Game
will have two Player
s: player1 and player2. It is possible for neither to win (i.e., there is no winner or loser). Let us update Game
with this new insight.
Note that I listed that player1 and player2 are of the type Player
. This is appropriate. Player
is a domain specific type (i.e., it is a conceptual notion). It is not an implementation detail.
We are now left with
- Rules ( Question Time )
- Score
WinnerLoser- Win
- Loss
- Stalemate (Tie)
- Turn
- Round
Let us tackle Round
. I think this is really meant to capture a single two-player turn sequence (i.e., player1 makes a move then player2 makes a move). English is fun… This probably should have been listed as a Verb Phrase in the form: player1 makes a single move followed by player 2. This is defined as one round or turn. Context is key. This is why the candidate lists are merely a starting point.
Let us update our Game
class…
I will apply a similar set of arguments to Win, Loss, and Stalemate. These are probably meant to check the results at the and of a single Game
… once the Game
is Over. We now have four new operations: isOver
, endedWithWin
, endedWithLoss
, and endedWithStalemate
. Let us update out model…
Wait… I do not like that. How do I know when a win occurs? What about a loss? What about a stalemate? I think there is some sort of process behind these checks. I think this is handled by the Referee
.
I am much happier with this. The Referee
checks for the win, loss, or stalemate. The Game
merely records if these have occurred. I am comfortable with this logic.
Let us add the relationship between Referee
and Board
.
Our PlantUML markup is now:
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
@enduml
I do not think a Game Board
can exist outside a Game
. Let us add the appropriate composition relationship.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
Game *--Board
@enduml
3 Constructing Useful UML Class Diagrams
We can also say that a Referee
is assigned to a Game
and that Referee
keeps Player
s honest
Before we continue… we should probably add isFull
to Board
. I think the Referee
might need this information for the stalemate check.
This leaves us with a fairly some fairly long PlantUML markup.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
isFull()
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
Game *-- Board
Referee --> "2" Player : keeps honest
Referee --> "1" Game : assigned to
@enduml
3.1 Forming Coherent Thoughts
What are we conveying in our current diagram? We are saying everything at once. We only have five (5) classes in this model. Imagine a larger model (i.e., one with at least 12 classes).
Could you make sense of such a digram? The answer is no.
Let us strip out the associations.
This captures our known Class interfaces
and attributes
. What do we want to convey? Now… Let us form coherent thoughts!
This diagram captures how players have a strategy. It is based on this strategy they select their moves. The referee ensures that not invalid moves are made (e.g., that players do not cheat).
This diagram captures the structure of a single game (i.e., one match between two players on a single board). Each board is fundamentally linked to a single game
3.2 What About Players?
We should probably consider Human and Computer Player
s. I will leave writing a description as an exercise to the reader.
This results in our last piece of PlantUML markup.. for now!
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
isHuman()
isComputer()
}
Player <|---- HumanPlayer
Player <|---- ComputerPlayer
@enduml
How would our previous C++, Java, and Python design decisions change?
4 Our Final Model
Nope… this is the last piece of markup. I guess I am a victim of the standard off-by-one error.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
isFull()
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player <|---- HumanPlayer
Player <|---- ComputerPlayer
@enduml