Skip to main content

Section 9.10 Inheritance, DRY Principle, and Code Reusability

One of the primary benefits of carefully planning your class hierarchy, particularly through the iterative refinement approach discussed earlier, is that it directly supports the DRY (Don’t Repeat Yourself) principle. The DRY principle emphasizes that you should avoid unnecessary duplication of code, logic, or data throughout your system. Proper use of inheritance is a powerful tool for adhering to this principle.

Subsection 9.10.1 Recognizing Opportunities to Stay DRY

As you’ve seen in the social media post example, duplication often signals opportunities to create superclasses or intermediate abstract classes:
  • If multiple subclasses (like TextPost, PhotoPost, and VideoPost) share identical fields (author, timestamp, likeCount, taggedUsers) or methods (like(), tagUser(), addComment()), these are prime candidates to move up into a common superclass (Post).
  • If you find specific subsets of subclasses (like PhotoPost and VideoPost) sharing specialized attributes or behaviors (such as handling fileSize, resolution, or generating thumbnails), consider introducing an intermediate class (MediaPost).
Explicitly documenting these overlaps in your Data Definitions step ensures you consciously spot and exploit these DRY opportunities.
Let’s examine a concrete example of how early inheritance planning supports the DRY principle:
public class Car {
    private double speed;
    private double positionX;
    private double positionY;
    
    public void accelerate(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed += amount;
    }
    
    public void brake(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed = Math.max(0, speed - amount);
    }
    
    // Other car-specific methods...
}

public class Bicycle {
    private double speed;
    private double positionX;
    private double positionY;
    
    public void accelerate(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed += amount;
    }
    
    public void brake(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed = Math.max(0, speed - amount);
    }
    
    // Other bicycle-specific methods...
}
public abstract class Vehicle {
    protected double speed;
    protected double positionX;
    protected double positionY;
    
    public void accelerate(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed += amount;
    }
    
    public void brake(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        speed = Math.max(0, speed - amount);
    }
    
    // Other common methods...
}

public class Car extends Vehicle {
    // Only car-specific fields and methods
    private double engineSize;
    
    // Car-specific methods...
}

public class Bicycle extends Vehicle {
    // Only bicycle-specific fields and methods
    private int numberOfGears;
    
    // Bicycle-specific methods...
}
Notice how the inheritance-based approach eliminates duplicated fields and methods, resulting in cleaner, more maintainable code.

Subsection 9.10.2 Inheritance and Long-Term Maintainability

Inheritance doesn’t just reduce code duplication, it significantly improves long-term maintainability:
  • Fixing a bug or making enhancements in a superclass (Vehicle) automatically benefits all subclasses (Car, Bicycle).
  • Consistency is easier to maintain across subclasses, ensuring a unified behavior for methods like accelerate() or brake().
  • Introducing new subclasses (e.g., Motorcycle or Scooter) becomes straightforward because the superclass already contains the essential shared behaviors.
For example, if we need to add position tracking to all vehicles, we only need to modify the Vehicle class:
public abstract class Vehicle {
    // Existing fields and methods...
    
    // New functionality added once, benefits all subclasses
    private List<Position> positionHistory = new ArrayList<>();
    
    public void recordPosition() {
        positionHistory.add(new Position(positionX, positionY));
    }
    
    public List<Position> getRouteHistory() {
        return new ArrayList<>(positionHistory); // Return defensive copy
    }
}
With this single change, every vehicle type now has position tracking capabilities, without having to modify any subclasses.

Insight 9.10.1. Why DRY Matters Beyond Initial Implementation.

At first glance, duplication may seem trivial. However, the true cost of duplication is usually realized during maintenance:
  • Every duplicated method is another place where you could introduce errors during bug fixes or feature enhancements.
  • Repeated logic requires repeated testing, increasing your testing burden.
  • Duplication often leads to subtle inconsistencies that degrade user experience and complicate debugging.
By carefully designing inheritance hierarchies that support DRY principles, you minimize these hidden maintenance costs.

Subsection 9.10.3 Using Interfaces to Complement Inheritance

While inheritance handles "is-a" relationships, interfaces are ideal for capturing cross-cutting capabilities. For example, suppose your traffic simulation application also supports other entities like TrafficLight and PedestrianCrossing, both of which can be "located on a map" or "toggled." However, these entities don’t share a direct inheritance relationship with Vehicle.
In this case, defining interfaces such as Mappable or Toggleable is a powerful complement to inheritance:
// Interface defining a capability to be placed on a map
public interface Mappable {
    double getPositionX();
    double getPositionY();
    void setPosition(double x, double y);
}

// Interface defining something that can be turned on/off
public interface Toggleable {
    void turnOn();
    void turnOff();
    boolean isOn();
}

// Vehicle superclass implements Mappable
public abstract class Vehicle implements Mappable {
    protected double positionX;
    protected double positionY;
    
    @Override
    public double getPositionX() {
        return positionX;
    }
    
    @Override
    public double getPositionY() {
        return positionY;
    }
    
    @Override
    public void setPosition(double x, double y) {
        this.positionX = x;
        this.positionY = y;
    }
}

// TrafficLight class implements both Mappable and Toggleable
public class TrafficLight implements Mappable, Toggleable {
    private double positionX;
    private double positionY;
    private boolean isOn;
    
    // Mappable implementation
    @Override
    public double getPositionX() { return positionX; }
    
    @Override
    public double getPositionY() { return positionY; }
    
    @Override
    public void setPosition(double x, double y) {
        this.positionX = x;
        this.positionY = y;
    }
    
    // Toggleable implementation
    @Override
    public void turnOn() { isOn = true; }
    
    @Override
    public void turnOff() { isOn = false; }
    
    @Override
    public boolean isOn() { return isOn; }
}
Using interfaces in conjunction with inheritance gives you the flexibility to model shared behaviors across unrelated class hierarchies, further enhancing reusability and adhering to DRY.

Insight 9.10.2. Abstraction vs. Implementation.

Remember that inheritance creates both an is-a relationship (abstraction) and a mechanism for code reuse (implementation). The abstraction aspect should drive your design decisions, with code reuse as a beneficial consequence.
A common mistake is to create inheritance hierarchies solely for code reuse without a meaningful abstraction. This leads to confusing designs where the "is-a" relationship doesn’t make intuitive sense. Always ask: "Does it make sense to say that SubclassX is a SuperclassY?" If not, consider composition or interfaces instead.
You have attempted of activities on this page.