Designing the Deck Class
A new Deck takes no constructor arguments and begins with 52 cards in a standard order, with every new deck identical to the others.
Guessing What a New Deck Holds
Imagine you are writing a card game and need a deck to play with. Before reading any method definitions, ask two simpler questions: what should a brand-new deck contain, and what should you have to tell the deck when you create it? Those two answers are the starting point for the specification of the Deck class, and they shape every method that follows. It is worth making your own guess first, because the specification is easier to follow once you know what problem it is solving.
What do you think happens?
Before reading on, what do you think a brand-new Deck contains, and does the caller need to supply anything to create it?
Reveal answer
A new Deck takes no constructor arguments and begins with 52 cards in a standard order. Every new deck is identical to the others.
Because the starting state is fixed by the specification, the caller has nothing to choose. Creating a deck is simply a matter of asking for one.
Tracing a Deck From Creation
The Deck is a stateful object. It begins in a known condition, and each method either changes that condition or reports on it. The most useful way to understand the class is to follow its state from the moment it is created. A new deck starts with 52 cards, all in a standard order, and that order is the same for every new deck. From that point on, the deck's contents change only through the operations the specification allows: dealing a card removes one from the deck, and shuffling puts used cards back and rearranges them.
Read the diagram as a set of states and transitions. The deck starts in the new state with 52 cards. Each dealCard() call moves it toward empty. Once the last card has been dealt, the deck is empty, and a further dealCard() call is an error. A shuffle() call returns used cards to the deck, so the deck can return to a full count from either the partly dealt state or the empty state. The diagram shows the card count and the reachable states, while the specification supplies the rules for each transition.
Why the Constructor Takes Nothing
The constructor builds the deck, and it takes no parameters. The specification defines the starting condition completely: 52 cards in a standard order, identical for every new deck. Because that condition is fixed, the caller has no decision to make when creating a deck. Supplying arguments would imply a choice the specification does not offer. Keeping the two starting conditions separate in your mind also prevents a common confusion. A new deck is in a standard order, which is the same every time. A shuffled deck is in a random order, which is not the same from one shuffle to the next.
The Three Operations on an Existing Deck
Once a deck exists, the specification gives it three operations: shuffle, dealCard, and cardsLeft. Each has a signature that tells a programmer what must be supplied and what comes back. Notice that only dealCard produces a value the caller must use. The other methods either change the deck's contents without returning anything, or return a count. This difference in return types is deliberate. It tells the caller what kind of information flows back from each operation.
Tracing a Deal Until the Deck Is Empty
Dealing From a Full Deck
A program creates a new Deck, deals every card, and then calls dealCard() once more. What does the caller observe at each step?
- 1Create the deck
The constructor takes no arguments. The deck starts with 52 cards in a standard order.
- 2Deal a card
dealCard()returns a Card to the caller. The caller receives whatever card the deck decided was next, and the remaining count drops by one. - 3Deal until none remain
Each
dealCard()call returns a Card. After the 52nd card is dealt,cardsLeft()reports zero. - 4Deal from the empty deck
The specification treats this call as an error.
dealCard()throws anIllegalStateException, which is documented in the method's comment as the behavior when no more cards are left.
The caller gets 52 Card values in turn. A 53rd call to dealCard() does not return a card. It throws an IllegalStateException.
The key point is where the behavior is specified. The fact that dealCard() throws an IllegalStateException on an empty deck is stated in the method's comment, which is part of the specification. A caller who reads the specification knows in advance that an empty deck is not a value to be handled quietly. It is an error the caller must avoid.
Guarding Against an Empty Deck
The surrounding program can avoid the error by checking the deck before dealing. The program asks cardsLeft() for the remaining count. If the count is greater than zero, it is safe to call dealCard(). If the count is zero, the program skips the deal. This check uses the deck's own answer, so it does not depend on a separate count kept by the program.
Mistakes When Using the Deck Contract
Common mistake Expecting a new deck to be shuffled
What it looks likeAssuming the first card of a freshly created deck is random.
Why it failsA new deck begins in a standard order that is the same for every new deck. Randomness comes from
shuffle().Do this insteadCall shuffle() if you need a random order, and remember that the constructor gives a standard one.
Common mistake Expecting shuffle() to return the cards
What it looks likeWriting a value = deck.shuffle() and expecting a card or list back.
Why it failsshuffle()returns nothing. The effect is seen only when the caller next askscardsLeft()or deals a card.Do this insteadCall shuffle() as a statement, then use cardsLeft() or dealCard() to observe the result.
Common mistake Dealing without checking for an empty deck
What it looks likeCalling dealCard() in a loop without ever asking cardsLeft().
Why it failsdealCard()on an empty deck throws anIllegalStateException, as the method's comment specifies.Do this insteadCheck cardsLeft() is greater than zero before each dealCard() call.
Common mistake Keeping a separate count of dealt cards
Why it failsA separate tally can drift out of step with the deck, and the specification already provides
cardsLeft()for this purpose.Do this insteadUse cardsLeft() as the single source of the remaining count.
Practice: Predicting a Deck's Behavior
A program creates a new Deck and immediately calls cardsLeft(). Then it deals 52 cards and calls shuffle(). Then it calls cardsLeft() again. Predict the value returned by each cardsLeft() call, and state what dealCard() would do if called after the 52 deals but before the shuffle.
Hints
- The constructor starts the deck with 52 cards.
shuffle()puts used cards back into the deck.- The specification says
dealCard()throws anIllegalStateExceptionwhen no cards are left.
Key Takeaways
- A new Deck takes no constructor arguments and begins with 52 cards in a standard order, identical to every other new deck.
shuffle()returns nothing, puts used cards back into the deck, and rearranges the cards into a random order.dealCard()takes no parameters and returns a Card. On an empty deck it throws anIllegalStateException, as stated in the method's comment.cardsLeft()reports the remaining count, so the program can ask the deck rather than track deals itself, and can check it before dealing.