Skip to main content

Section 9.11 Conclusion: Designing Class Hierarchies

Throughout this chapter, we’ve explored how to thoughtfully integrate inheritance into the design recipe process. By considering inheritance relationships early, during Steps 0, 1, and 2, we create more robust, maintainable, and extensible class hierarchies that directly support the DRY principle and lead to cleaner, more intuitive code.

Subsection 9.11.1 Key Takeaways

As you move forward with implementing inheritance in your own projects, keep these important principles in mind:
  • Start with the problem domain: Effective inheritance hierarchies reflect natural "is-a" relationships in your problem space. During Step 0, look for phrases like "all posts need..." or "every vehicle has..." that suggest common attributes or behaviors suitable for a superclass.
  • Document potential hierarchies early: Create simple diagrams or tables that categorize common versus specialized attributes, helping you visualize the inheritance structure before writing any code.
  • Be intentional about shared attributes: During Step 1 (data definitions), place fields at the appropriate level in the hierarchy, with common fields in superclasses and specialized fields in subclasses. Consider creating intermediate superclasses when multiple classes share specific attributes.
  • Design clear method contracts: In Step 2, create purpose statements that explicitly communicate whether subclasses should, may, or must not override each method. These contracts form the blueprint for how your classes will interact.
  • Refine iteratively: As you progress through Steps 0-2, continuously revisit and refine your hierarchy. When you find identical or similar methods across classes, consider moving them up to a common superclass.
  • Use interfaces for cross-cutting concerns: When behavior doesn’t fit neatly into the inheritance hierarchy, interfaces provide a powerful complementary mechanism for sharing functionality across unrelated classes.
  • Keep the DRY principle central: Good inheritance design naturally supports the Don’t Repeat Yourself principle, reducing duplication and improving maintainability. Look for opportunities to consolidate repeated code without forcing unnatural "is-a" relationships.

Subsection 9.11.2 Inheritance Beyond Design

While we’ve focused on integrating inheritance into the early steps of the design recipe, its benefits extend through implementation and maintenance:
  • Implementation flows naturally from a well-designed hierarchy, with clear separation of responsibilities between superclasses and subclasses. Common code is written once, specialized behavior is isolated to the appropriate subclasses.
  • Testing becomes more structured, allowing you to test common behavior once at the superclass level while focusing subclass tests on specialized behavior. This reduces redundant testing effort and makes your test suite more maintainable.
  • Code evolution is simplified when new requirements emerge. Adding features that apply to all classes can be implemented once in the superclass. New subclasses can be introduced with minimal code by leveraging existing functionality.
  • Bug fixes propagate automatically when fixed in superclass methods, benefiting all subclasses that inherit that functionality without requiring changes to each individual class.
Remember that inheritance is just one tool in your design toolbox. For some relationships, composition or interfaces might be more appropriate. The key is to make deliberate choices during the design phase rather than trying to retrofit inheritance later which is a process that’s often more complex and error-prone.

Subsection 9.11.3 Integrated Inheritance Example: Social Media System

Throughout this chapter, we’ve explored many concepts related to inheritance and class hierarchies. The following example brings these concepts together in a single, runnable program that demonstrates a social media post system—the example we’ve referenced throughout the chapter.
This example showcases:
  • A class hierarchy with an abstract superclass (Post)
  • An intermediate class (MediaPost) providing shared functionality
  • Concrete subclasses with specialized behavior
  • Constructor chaining with super()
  • Method overriding with @Override annotation
  • The DRY principle in action
Note: To work within Runestone’s constraints, all classes are combined in a single file. In a real project, each class would typically be in its own file.
Try experimenting with this code! Here are some things you might modify:
  • Create a new type of post (like PollPost or EventPost) by extending Post
  • Add new fields to the existing classes
  • Override like() in one of the subclasses to provide special behavior
  • Add a method to count hashtags in TextPost
  • Implement a share() method in the Post class and customize it in subclasses
This example illustrates how a well-designed inheritance hierarchy can:
  • Reduce code duplication by centralizing common fields and methods in the superclass
  • Use abstract classes for conceptual entities that shouldn’t be instantiated directly
  • Create intermediate classes when a subset of subclasses shares specialized behavior
  • Allow for polymorphism through method overriding
  • Establish a clear "is-a" relationship that mirrors real-world concepts
By understanding these inheritance principles and applying them with the design recipe, you can create cleaner, more maintainable, and more extensible code in your own projects.
You have attempted of activities on this page.