Skip to main content

Section 9.7 Step 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:

Subsection 9.7.1 Creating Inheritance-Aware Data Definitions

Start by clearly documenting each class’s attributes, categorizing them into shared versus specialized:
Class Shared Attributes and Methods Specialized Attributes and Methods
Post (superclass)
  • author (String)
  • timestamp (Date)
  • likeCount (int)
  • comments (List<Comment>)
  • like()
  • addComment(Comment)
None (only shared fields here)
TextPost Inherits all from Post
  • textContent (String)
  • wordCount (int)
MediaPost
(intermediate superclass)
Inherits from Post
  • fileSize (int, in KB)
  • resolution (String, e.g., "1920x1080")
PhotoPost Inherits from MediaPost
  • imageFile (String)
  • caption (String)
VideoPost Inherits from MediaPost
  • videoFile (String)
  • description (String)
  • duration (int, seconds)
Clearly listing attributes in this way makes inheritance relationships visually apparent and helps you identify the logical placement of fields and methods.
Let’s see how this translates to Java code:
/**
 * 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.

Subsection 9.7.2 Documenting Relationships with Diagrams

A simple class diagram or textual hierarchy helps visualize these relationships explicitly:
Post (author, timestamp, likeCount, comments, like(), addComment())
├── TextPost (textContent, wordCount)
└── MediaPost (fileSize, resolution)
      ├── PhotoPost (imageFile, caption)
      └── VideoPost (videoFile, description, duration)
This visual documentation provides a quick reference during implementation and helps ensure your class hierarchy remains clear and consistent as your project evolves.

Subsection 9.7.3 Handling Constraints in Hierarchies

When defining data for a class hierarchy, pay special attention to invariants (conditions that must always hold true) and where they belong:
  • Superclass invariants apply to all subclasses (e.g., author must not be null for all posts)
  • Subclass invariants apply only to that specific type (e.g., video posts must have a positive duration)
Document these constraints clearly, as they will inform validation logic in constructors and methods. For example:
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.

Insight 9.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.

Insight 9.7.2. Leveraging Invariants & Preconditions with Inheritance.

Inheritance isn’t just about sharing attributes and behaviors; it’s also about enforcing shared constraints and invariants. For instance:
  • All Post subclasses must ensure the timestamp is always set at creation.
  • MediaPost subclasses must guarantee that fileSize and resolution are always valid and non-empty.
Clearly documenting these invariants at the superclass level can simplify validation logic, avoid redundancy, and improve maintainability.
You have attempted of activities on this page.