Designing Classes and Objects
1. Defining Class Responsibilities
| Tool | Use |
|---|---|
| CRC card | Class, Responsibility, Collaborators |
| Single sentence | "This class X by Y" — if "and" appears, split |
| Responsibility list | ≤ 3 cohesive actions |
2. Implementing Constructors
| Pattern | When |
|---|---|
| All-args | Few required fields |
| Static factory | Named creation, return subtype |
| Builder | ≥ 4 fields or many optional |
| Copy constructor | Defensive copies |
| Telescoping | Avoid; use builder |
Example: Validating constructor
public Email(String value) {
Objects.requireNonNull(value, "email");
if (!value.contains("@")) throw new IllegalArgumentException("invalid email");
this.value = value.toLowerCase(Locale.ROOT);
}
3. Implementing Encapsulation
| Rule | Implementation |
|---|---|
| Private fields | Always |
| No setters | Prefer behavior methods (deposit, not setBalance) |
| Return immutable | List.copyOf on getters |
| Hide collaborators | Don't expose internal collections directly |
4. Designing Method Signatures
| Aspect | Guideline |
|---|---|
| Name | Verb phrase, intention-revealing |
| Param count | ≤ 3; group with object if more |
| Param order | Required → optional → callbacks |
| Boolean flags | Avoid; split into two methods |
| Return type | Most abstract type useful to caller |
| Null returns | Use Optional or empty collection |
5. Implementing Static vs Instance Members
| Member | Use Static | Use Instance |
|---|---|---|
| Field | Constants, shared cache | Per-object state |
| Method | Stateless utilities, factories | Operates on fields |
| Mock | Hard (needs Mockito static) | Easy |
6. Designing Immutable Classes
| Step | Detail |
|---|---|
| Class | final |
| Fields | private final |
| Setters | None |
| Mutable refs | Defensive copy in/out |
| "Modify" | Return new instance (with*) |
Example: Record as immutable
public record Money(long cents, Currency currency) {
public Money plus(Money o) {
if (!currency.equals(o.currency)) throw new IllegalArgumentException();
return new Money(cents + o.cents, currency);
}
}
7. Implementing Utility Classes
| Convention | Why |
|---|---|
final class | Prevent subclass |
| Private constructor | Prevent instantiation |
All static | Stateless helpers |
| Pure functions | Predictable behavior |
8. Implementing Value Objects
| Trait | Detail |
|---|---|
| Identity | By value (equals/hashCode on fields) |
| Immutable | Required |
| Self-validating | Throw in constructor |
| Examples | Money, Email, DateRange |
9. Implementing Entities
| Trait | Detail |
|---|---|
| Identity | By stable ID, not field values |
| Lifecycle | Created → modified → archived/deleted |
| equals/hashCode | Use ID only |
| Behavior | Methods enforce invariants |
10. Understanding Composition vs Aggregation
| Relationship | Lifetime | UML |
|---|---|---|
| Composition | Owned (dies with parent) | Filled diamond |
| Aggregation | Shared (independent) | Hollow diamond |
| Association | Uses, no ownership | Plain line |
11. Implementing Data Classes
| Java Option | Use Case |
|---|---|
record Java 16+ | Immutable data carriers |
Lombok @Value | Pre-Java 16 immutables |
Lombok @Data | Mutable DTOs (use carefully) |
| Plain class | Need custom equality / behavior |
12. Designing Object Lifecycle
Created → Initialized → Active ↔ Suspended → Archived → Disposed ↑ ↑ constructor invariants enforced
| Phase | Concern |
|---|---|
| Construct | Validate all required state |
| Init | Avoid escaping this in ctor |
| Use | Maintain invariants per method |
| Dispose | AutoCloseable + try-with-resources |