Concepts / Designing Reusable Classes

Designing Reusable Classes

The Card class uses named constants such as SPADES and ACE so that suits and values are readable instead of being bare integer codes.

  • Programming
Interactive lab

Try it: Names and Objects

How Python variables are names bound to objects: assignment never copies, mutating a list is seen through every name that points at it, and ints are replaced rather than changed.

How it works

  1. name = object binds a name; b = a makes b point at the same object.
  2. Mutating a list (append, +=) changes the one shared object.
  3. a = a + [x] and y = y + 1 create NEW objects and re-point one name.
  4. Passing a list to a function passes the reference, so the function can change it.

Default run (7 steps): Two names, one list. Every name is a label that points at an object. … Printed: [1, 2, 3] / True

Simplified: Six fixed scripts on a tiny heap; you choose the values. Object numbers are illustrative, not real id() values.

Educational simulation

Loading the simulation…

Why a Card Should Never Change

This article starts from a complete, working Java class that represents a playing card from a standard poker deck, including jokers. The class is written to be reused. The effort that goes into designing a class like this, writing it carefully, and testing it tends to pay off across many programs, which is why the design decisions matter more than the individual lines of code.

Two design decisions shape the class. First, the suits and values are named with constants instead of bare numbers, so the code reads like English. Second, a Card never changes after it is created. If your program needs a different card, it builds a new Card object instead of editing the old one. The questions to keep in mind as you read are where the suit and value numbers come from, and what would have to happen for a card to end up holding a different suit later on.

What do you think happens?

A card is created with a particular suit and value. Could a later statement in the program overwrite either field directly from outside the class?

  • Yes, any code that holds a reference to the card can overwrite the field

  • No, the fields are private and final, so outside code cannot assign to them

Reveal answer
Answer:

No, the fields are private and final, so outside code cannot assign to them

The fields are declared private, so code outside the class cannot reach them, and final means they are set only in the constructor. Keep this prediction in mind, because the rest of the article explains exactly how the class enforces it.

Reading Suits and Values Without Magic Numbers

A suit could be stored as a plain integer. Four suits would then be stored as 0, 1, 2, and 3. The trouble is that a reader who meets the number 2 in a program has no way to tell which suit it means without looking up a table somewhere else. The number carries no meaning on its own. Code full of bare numbers is hard to read and easy to get wrong, because a single misremembered mapping produces a bug that looks perfectly valid.

The Card class solves this by declaring named constants. Each suit has a name, such as SPADES, and the name stands in for the integer code behind it. Code that refers to Card.SPADES reads as English, while code that refers to a bare number requires the reader to remember the mapping.

java
Notes on the code
  • The first comparison hides its meaning inside the number 2.
  • The second comparison names the suit directly, so a reader does not need a lookup table.
  • The numeric codes in this illustration are hypothetical. The article's source does not list the integer behind each suit name.
ACE1JACK11QUEEN12KING132 through 10keep numeric meaning
How does each named value correspond to the underlying integer, and how does a Card's stored value map back to its readable name?

The same idea covers card values. The numbers 1 through 13 are used, but the special ones have names: ACE is 1, JACK is 11, QUEEN is 12, and KING is 13. The values 2 through 10 keep their numeric meaning. The class also defines JOKER as a suit code alongside the four real suits, which shows that the suit constants do not all describe ordinary suits. A reader who sees Card.JOKER understands the card without any further lookup.

Tracing a Card's State After Construction

The class is easiest to understand as a set of parts that depend on each other. The constants form the vocabulary of suits and values. The private final fields store one suit and one value. The constructor fills those fields, and the getters expose them for reading. Follow a single card through this chain. When a card is created, the constructor runs once and sets both fields. From that point on, the card's state is fixed.

Card constructedsuit and value setfields fixedAssignment attemptCompile errorno changeconstructor runs onceoutside code tries to writerejected
What is the Card's state right after construction, and what happens to that state when code tries to assign a new suit or value?

Read the diagram as a timeline. The card is born with a suit and a value, and no later statement is allowed to change them. An attempt to write a new suit is an event that the class rejects, so the card stays in the state it was given. This is the core of what the source calls immutability.

Two Layers That Make a Card Immutable

An immutable object is one whose state cannot be changed after it has been created. A Card fits this definition. Its two instance variables, one for the suit and one for the value, are set once and then left alone for the rest of the object's life. Immutability is not a single keyword. It comes from a combination of decisions that work together, and each decision covers a different gap.

Tracing a Failed Modification

Now trace what happens when code tries to change a card. The example below is an illustrative version of the Card class and two attempts to modify it. The first attempt comes from outside the class. The second comes from inside a method that tries to reassign the suit.

java
Notes on the code
  • The numeric suit codes are illustrative. The source does not state the integer behind each suit name.
  • suit and value are private final, so both fields are set only in the constructor.
  • The outside attempt is the first line that breaks the rules.
  • The inside attempt is rejected too, because the field is final even though the method is inside the class.
Output
error: suit has private access in Card
error: cannot assign a value to final variable suit
c.suit =Card.HEARTSoutside codeIs suit private?suit has privateaccess in Cardthis.suit = sinside changeSuitIs suit final?cannot assign a valueto final variablesuitcompiler checks accessyes, privatecompiler checks modifieryes, final
When code attempts to modify a Card, which statement runs first, where is the attempt rejected, and which declaration causes the failure?

Trace the outside attempt first. The statement c.suit = Card.HEARTS asks to write to the suit field from outside the class. The compiler checks access before anything else, sees that suit is private, and rejects the statement with the message about private access. The line responsible is the declaration private final int suit, because its private modifier is what blocks the outside write. The inside attempt, this.suit = s, gets past the access check because it lives inside the class, but the compiler then rejects it because suit is final. In that case the responsible declaration is again the field declaration, this time through its final modifier.

Creating a New Card Instead of Changing One

Because every instance variable is final, the Card as a whole is immutable. A card cannot be altered in place, so a program that wants a different card must create a new Card object rather than editing an existing one. This is a design choice, not a limitation to work around. Each Card value keeps the same contents for its whole life, so it can be shared, stored, and passed around without anyone worrying that it changed underneath them.

java
Notes on the code
  • Replacing a card means creating a new object, not editing the old one.
  • The original card is untouched, so other parts of the program holding it still see the same value.

The Joker Shortcut Constructor

The class includes two constructors. The one that takes an explicit suit and value is the general form. The no-argument constructor is a shortcut for one specific card: it creates a joker with the value 1. The source describes this as equivalent to asking for a card with value 1 and suit JOKER. Both constructors still fill the same two final fields, so the shortcut does not weaken immutability.

Mistakes Learners Make with Card Immutability

  • Common mistake

    Expecting to change a card's suit with a setter-style assignment from outside the class

    What it looks like
    c.suit = Card.HEARTS;
    Why it fails

    The suit field is private, so code outside the class cannot reach it, and it is final, so it cannot be reassigned after construction.

    Do this instead

    Create a new Card with the suit you want, such as new Card(Card.HEARTS, value).

  • Common mistake

    Thinking that final alone makes the whole class immutable

    Why it fails

    Final stops reassignment of a field, but the class also needs private fields and getters that only read. Without private access, outside code could still write to the field directly.

    Do this instead

    Treat immutability as the combination of private fields, read-only getters, and final fields set in the constructor.

  • Common mistake

    Using bare integers such as 2 for suits and 13 for kings

    What it looks like
    if (c.getValue() == 13)
    Why it fails

    The number carries no meaning on its own, and the reader must look up a mapping somewhere else to understand the line.

    Do this instead

    Compare against the named constant, such as Card.KING, so the code reads as English.

  • Common mistake

    Assuming the error message names the line that caused the problem instead of reading it for the modifier that blocks the write

Practice Tracing a Card

MEDIUM

Using the illustrative Card class from the failed modification example, predict whether the following statement compiles: Card c = new Card(Card.CLUBS, Card.QUEEN); c.getValue() returns 12. Then explain which line in the class would reject an attempt to write c.value = 13 from outside, and which constant you would use instead of 13 for a king.

Hints
  • Look at which keyword controls access to value and which keyword controls whether it can be reassigned.
  • The source lists KING as the named constant for the value 13.
  • The getter getValue returns the stored value and does not change it.
EASY

Explain in two or three sentences why changing a card's value requires creating a new Card object, and name the two parts of the class design that make the change impossible in place.

Hints
  • Refer to access control and the final keyword.
  • Remember that getters only read.

Summary of Card Design Decisions

  1. 1
    Named constants such as SPADES, ACE, and JOKER replace bare integer codes, so code that uses them reads as English.
  2. 2
    Private final fields, set only in the constructor, make a Card immutable.
  3. 3
    Getters getSuit and getValue allow reading but not writing.
  4. 4
    To represent a different card, a program creates a new Card object rather than modifying an existing one.
  5. 5
    Failed modifications are rejected by the compiler, and the error message points to the private or final declaration responsible.

At a glance

  • Named constants such as SPADES, ACE, and JOKER replace bare integer codes, so code that uses them reads as English.
  • A Card is immutable because its suit and value fields are private, final, and set only in the constructor.
  • Getters getSuit and getValue allow outside code to read a card but never change it.
  • To represent a different card, create a new Card object instead of modifying an existing one.
  • When a modification fails, the compiler's error message points to the private or final declaration that blocks it.