Section9.7Step 1: Data Definitions & Class Hierarchy Design
In Step 1 of the Design Recipe, you typically define the structure and constraints of the data your classes or methods will use. When considering inheritance, this step becomes particularly important because it establishes how your classes relate and what common structures should be shared through inheritance.
Continuing our social media application example from Step 0, let’s explicitly define the data structures for our classes and organize them to highlight potential superclasses:
Clearly listing attributes in this way makes inheritance relationships visually apparent and helps you identify the logical placement of fields and methods.
/**
* Represents a social media post with author, timestamp, likes, and comments.
* Invariants:
* - author must not be null or empty
* - timestamp must not be null
* - likeCount must be non-negative
*/
public abstract class Post {
// All posts have these common attributes
protected String author;
protected Date timestamp;
protected int likeCount;
protected List<Comment> comments;
// No fields for text content, media files, etc. - those belong in subclasses
}
/**
* Represents a text-only post.
* Invariants:
* - textContent must not be null
* - wordCount must match the actual number of words in textContent
*/
public class TextPost extends Post {
private String textContent;
private int wordCount;
}
/**
* Represents a post containing media (abstract intermediate class).
* Invariants:
* - fileSize must be positive
* - resolution must be in format "WidthxHeight" (e.g., "1920x1080")
*/
public abstract class MediaPost extends Post {
private int fileSize; // in KB
private String resolution;
}
Notice how the shared fields (author, timestamp, likeCount, comments) are defined in the superclass, while specialized fields are in their respective subclasses. The comments also document the invariants for each class.
This visual documentation provides a quick reference during implementation and helps ensure your class hierarchy remains clear and consistent as your project evolves.
public abstract class Post {
// Fields as before...
// Constructor enforces superclass invariants
public Post(String author, Date timestamp) {
// Enforce invariant: author must not be null or empty
if (author == null || author.isEmpty()) {
throw new IllegalArgumentException("Author must not be null or empty");
}
// Enforce invariant: timestamp must not be null
if (timestamp == null) {
throw new IllegalArgumentException("Timestamp must not be null");
}
this.author = author;
this.timestamp = timestamp;
this.likeCount = 0;
this.comments = new ArrayList<>();
}
}
public class VideoPost extends MediaPost {
// Fields as before...
// Constructor enforces both superclass and subclass invariants
public VideoPost(String author, Date timestamp, int fileSize, String resolution,
String videoFile, String description, int duration) {
// Call superclass constructor to enforce its invariants
super(author, timestamp, fileSize, resolution);
// Enforce subclass invariants
if (videoFile == null || videoFile.isEmpty()) {
throw new IllegalArgumentException("Video file must not be null or empty");
}
if (duration <= 0) {
throw new IllegalArgumentException("Duration must be positive");
}
this.videoFile = videoFile;
this.description = description;
this.duration = duration;
}
}
This pattern ensures that invariants are enforced at the appropriate level in the hierarchy.
Insight9.7.1.Honoring Superclass Guarantees in Subclasses.
When defining data constraints, keep in mind a fundamental principle of inheritance: A subclass should behave in a way that anyone using a superclass reference would expect, even if they don’t know the actual subclass type.
This means subclasses should not add stricter requirements than their parent class or provide weaker guarantees. For example, if the Post class allows any non-empty author string, a TextPost subclass shouldn’t restrict author names to a specific format - that would break the expectation set by the superclass.