09 Classes and Class Design

A practical guide to designing Java classes that model concepts, protect state, enforce rules, and use relationships effectively.

Modeling Concepts with Classes

A Java is a programmer-defined type that combines state and behavior. Fields store state, methods provide behavior, and an is an instance of a created at run time.

When modeling a concept such as BankAccount, Student, or Thermostat, identify:

  • The important state that must be stored.

  • The behavior the should provide.

  • The rules that must always remain true.

  • The other objects it may need to use.

A bank account might store an account number and balance. Its methods could include deposit, withdraw, and getBalance. The should enforce rules such as rejecting a negative initial balance and refusing a withdrawal that exceeds the available balance.

A strong design keeps clients focused on what the can do rather than on how its data is stored.

Takeaway: Begin with a meaningful concept, then connect its state, behavior, rules, and collaborators.

Constructors and Initialization

A typical uses fields for state, a to establish an initial valid state, and methods to provide behavior or controlled access to state.

A has the same name as its and no return type. It is called when an is created with new. Constructors should validate arguments when necessary so that an cannot begin in an invalid state.

A may provide multiple constructors through overloading. The constructors must have different parameter lists. One can delegate to another by using this(...), which must be the first statement in the .

Inside an instance method or , this refers to the current . It is especially useful when a parameter has the same name as a field, as in this.name = name.

Example: A Rectangle can reject nonpositive dimensions, store the valid width and height, and let an area method calculate the rectangle's area.

Takeaway: Constructors establish valid initial state; methods define the operations that are allowed afterward.

and Access Control

protects an 's state by limiting direct access to its representation. Fields should usually be private, while public methods should expose only operations that make sense for the concept.

For example, a bank account should not expose a public balance field that any caller can replace with an invalid value. Instead, it can provide a deposit method that accepts only positive amounts and a withdraw method that checks both the requested amount and the current balance.

Access modifiers control visibility:

  • private members are accessible only within the declaring .

  • Members with no modifier are accessible within the same package.

  • protected members are accessible within the package and, subject to Java's protected-access rules, in subclasses.

  • public members are accessible to other code.

A getter may provide read access without allowing arbitrary replacement. A setter should be added only when unrestricted replacement is appropriate. Choosing the most restrictive suitable access level also allows the internal representation to change without requiring changes from client code.

Takeaway: Hide data, expose meaningful operations, and make every operation preserve the 's rules.

Instance and Static Members

An instance member belongs to one particular . Each Player can have its own instance field such as name. A static member belongs to the itself and is shared by all objects of that .

Use a when a value describes the as a whole, such as a count of all Player objects. Use a static method when an operation does not need a particular 's state, such as MathTools.square.

A static method cannot directly use instance fields, instance methods, or this because it has no particular . It can directly use static members. Static members are normally accessed through the name.

The combination static final is commonly used for a -level constant. static means the member belongs to the , while final means the field cannot be reassigned after initialization.

Takeaway: Choose instance members for -specific state and behavior; choose static members for -wide state or operations.

Responsibilities and Public Interfaces

The of a should be small, clear, and difficult to misuse. Decide which responsibilities belong to the , which data must remain private, which operations should be public, and whether objects should be changeable after construction.

A cohesive keeps related data and behavior together. The principle of single responsibility suggests that a should generally have one central responsibility. For example, a PayrollCalculator should calculate payroll rather than also handling user-interface drawing, database connections, and file backups unless those responsibilities are deliberately part of its design.

A useful design process is:

  1. Extract nouns that may become classes, such as Order, Customer, and Product.

  2. Extract verbs that may become methods, such as addItem, cancel, and calculateTotal.

  3. Assign each action to the best suited to perform it.

  4. List the state each must store.

  5. Identify invariants that must always remain true.

  6. Hide the representation behind necessary operations.

  7. Test normal, boundary, and invalid cases.

For an online order, separating Order, OrderItem, Product, and Customer gives each a clearer responsibility and makes the design easier to test and change.

Takeaway: Good interfaces make valid use easy, invalid use difficult, and responsibilities easy to understand.

and

expresses an is-a relationship. A Bicycle can extend Vehicle when a bicycle genuinely satisfies the meaning of being a vehicle. The subclass can use accessible behavior from the superclass and add behavior of its own.

A subclass can call a superclass with super(...). Constructors are not inherited, and private superclass members are not directly accessible to subclasses. A superclass should expose protected or public behavior only when that access is part of the intended design.

expresses a has-a or uses-a relationship. A Car can contain an Engine and delegate starting behavior to that engine. often provides more flexibility because a can work with different component objects without forming a rigid relationship.

Choose only when the subtype truly satisfies the superclass's meaning. Choose when the mainly needs another 's functionality.

Takeaway: Use for a genuine is-a relationship and for assembled or delegated behavior.