Completing the Console Adapter¶
Do This First...
Be sure to have completed prior labs before starting.
You can also use the solution from the original repository and do a checkout of the tag lab5-start
:
Goal¶
Move all console-specific behavior to the new ConsoleGameAdapter
class that's been provided.
Why: to "purify" the Game class, making it I/O-Free. I/O-Free means that our Domain Objects must not reference I/O (directly or indirectly), in order to make the code easy to test and tests that run fast (on the order of a second or two).
A. Update the Codebase¶
You may skip this step if...
If you did a git checkout lab5-start
, then the classes and changes described in the next few steps have already been done for you and you can skip to Fix Compile Errors below. You might want to read the steps anyway to find out what has changed.
Add New Classes¶
To jump-start this lab, I've provided two new classes: Blackjack.java
and ConsoleGame.java
.
The ConsoleGame
has compile errors that you will fix below, so leave them alone for now.
You'll also need to add a field and some methods to the Game
class.
New Field in Game
¶
Add this field to the Game
class:
This keeps track of the state of the player: are they still able to perform actions, or are they done (see the new methods below).
New Methods for Game
¶
Add these methods to the Game
class (I put them at the end):
public void playerHits() { // (1)!
playerHand.drawFrom(deck);
playerDone = playerHand.isBusted(); // (2)!
}
public void playerStands() { // (3)!
playerDone = true; // (4)!
}
public boolean isPlayerDone() { // (5)!
return playerDone;
}
- Command method representing the player's Hit action.
- If the player goes bust, then they are "done".
- Command method representing the player's Stand action.
- If the player stands, by definition they are "done".
- Query method of current state.
Update pom.xml
¶
The Blackjack
class above replaces the main()
method in Game
, so you'll need to update the pom.xml
file so that it starts the game using the new class. Find this line:
and replace domain.Game
with Blackjack
:
And since we're changing the entry point into the application, let's make sure to update our GameDisplayTest
. Replace
with
B. Fix Compile Errors¶
Now you can fix the compile errors in ConsoleGame
that are due to non-public visibility. Generally, making them public
will be sufficient. Later, after they're moved to the right class, you'll reduce their visibility.
Run Tests & Play Game¶
To check that you've done everything correctly, run all the tests: they should pass. Run the game from the console, and you might run into a small problem. See if you can figure it out before looking at the solution.
Hint
When and where does the Scanner
get initialized? Can we initialize it somewhere else?
C. Move Console I/O to Adapter¶
Less Guidance
Unlike prior labs, this section is less prescriptive, leaving it to you to figure out the right thing to do. Don't hesitate to ask for help or guidance in the Discord (or via email if you're not on Discord).
Now that everything is as it was, at least from the outside (playing the game), we can work on completing the separation
of I/O-dependent code from the domain-specific logic. Your goal is to do this to the Game
class by moving all code
relating to Console I/O into the new ConsoleGame
class. You'll know you're done when the Game
class is I/O-Free.
- For example, in
determineOutcome
, change theSystem.out.println
to return aString
and adjust the calling code as needed. -
You will need to expose
Game
information via Query methods (this is the Encapsulation vs. I/O-Free tradeoff). Use these guidelines when creating the new methods:Query Method Guidelines
When exposing data with new Query Methods, make sure they follow these guidelines:
-
Unchanging: The returned data is a point-in-time view of the data. When you do a "query", even against an object, you want to know what the information was at the point you asked for it, and don't expect the result to change even if the underlying object's data does change. Returning a copy can be useful, or returning an immutable object.
-
Integrity: Prevent consumers of the data from changing the object's internal state. It helps if the return value doesn't mislead clients into thinking it is providing access to internal state, therefore avoid returning mutable objects.
-
Safe: Calling the Query should not change the observable state of the object. This is also known as having no side-effects.
Think and Discuss...
When creating the query methods for the player and dealer hands (
playerHand()
anddealerHand()
), what would returningHand
mean to the caller?Click for an Answer
Returning mutable objects (like
Hand
) might lead the caller to believe that changes to that object will change the state of the actual hand, which breaks encapsulation. Even returning a copy is insufficient as the calling code still looks like it has a mutable object, but changing it does not affect the game's state.Think about all of the options for the return values from these methods (including bad ones). I've come up with four different options, can you come up with more? Share your thoughts and ideas in Discord.
-
Use automated refactorings as much as you can, such as Make Static (Refactor menu > Make Static...
) and Move Members (F6).
Need Help?
This section is a lot of work. Struggling to get it right is how you learn, but if you find yourself struggling for too long, don't hesitate to ask for help on the Discord or via email. For those of you in the Group Guided Tour, you can also discuss and get help during the Group Coaching Hour.
D. Tidy The Code¶
Once you've moved all Console-related code out of the Game
class, don't forget to tidy up the code in both the Game
class as well as the ConsoleGame
class.
Things to think about as you tidy:
-
What code is now no longer necessary in
Game
? -
What static methods in
ConsoleGame
can be turned into instance methods? -
What does the public API of
Game
need to be, i.e., what methods can be made private?Think About...
What do you think of the names of the classes in the
com.r2ha.blackjack.adapter.in.console
package? How about the methods in those classes? Do they make it obvious what they're doing?Click for an Answer
The names
ConsoleGame
,ConsoleCard
, andConsoleHand
don't say much about the abstraction that they represent, or the roles that those objects play. What are some better names that would make their purpose more clear? How aboutdisplay
in theConsoleCard
class anddisplayFaceUpCard
in theConsoleHand
class? Discuss in the Discord.
E. Play the Game!¶
While we have a reasonable set of characterization tests, it's still a good idea to play the game to see that everything continues to work as expected.
Remember that you'll want to do this from a full command terminal.
You're Done
That's the end of this lab. Well done!
If you want to compare your solution with mine, you can checkout from the main repository using the tag lab5-solution
.