← Skill tree CS Skill Tree 0 CSCD211

Favor Composition over Inheritance

Textbook: BJP (Reges and Stepp)

Before You Start

Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.

Not sure? Take the 60-second self-check.

1. IS-A consequence. If class B extends A, what can code that takes an A parameter do with a B object?

Check

Accept it directly. B IS-A A; the substitutability guarantee means every operation valid on A is valid on the actual B object at runtime.

2. HAS-A mechanic. How does a class control which methods it exposes from a composed object?

Check

By explicitly writing delegation methods that call the composed object’s methods. Only what is explicitly delegated is visible to callers. Methods the outer class does not delegate remain hidden.

3. Inheritance coupling. Why is a subclass “bound forever” to its superclass’s API?

Check

Every public method of the superclass is part of the subclass’s public interface, whether the subclass wants it or not. If the superclass adds a new public method in a future version, every subclass inherits it automatically, which may break the subclass’s invariants.


Try This First

You want a LoggingList<T> that behaves like a List<T> but prints a log line on every add call. Two options:

Before reading: which fails if ArrayList changes the internal implementation of addAll to no longer call add?

Check

Option (A) fails. The override of add assumes that addAll calls add internally. If ArrayList changes so that addAll writes directly to the backing array, the logging override is bypassed without any warning. Option (B) is immune: it wraps at the public API boundary and delegates addAll explicitly.


What You Need To Walk In With

The Insight: Inheritance is not a code-reuse mechanism; it is a substitutability mechanism. Using extends when you want to reuse methods gives callers access to everything the superclass provides, whether you want them to have it or not, and ties your class forever to every change the superclass makes. Composition gives selective control: expose only what you delegate, and change the backing implementation at will.

Once you see the MyArrayList split-state failure clearly, you also see the corrective: drop extends, implement only the interfaces the class genuinely needs, and delegate explicitly. From there you can identify “inheriting for code reuse” in any codebase, choose between composition and inheritance for a concrete design scenario, and justify that choice in terms of substitutability, coupling, and API surface. You can: state Bloch’s rule and its four reasons, diagnose the split-state anti-pattern, apply the composition-or-inheritance decision to a novel type, and articulate exactly what changes in the Bloch-corrected form of MyArrayList and why.


How It Works

Four reasons to prefer composition

1. Encapsulation. A composed class exposes only the methods it explicitly delegates. A subclass exposes every public method of the superclass, including ones that violate the subclass’s invariants if called.

2. Loose coupling. A composed class holds a reference of an interface type (e.g., List<T>). The backing implementation can be swapped at construction time. A subclass inherits from exactly one concrete class and cannot switch parents.

3. Multiple HAS-A. A class can hold fields of multiple types. Java allows only single class inheritance. Composition is unlimited; inheritance is one.

4. No permanent coupling. You can stop composing a class by removing the field. You cannot remove extends without breaking every call site that treated the subclass as the superclass type.

When IS-A is the right choice

Use inheritance when the “IS-A” relationship is genuine and substitutability is required:

Do NOT use inheritance to reuse methods from a class you do not control. Do NOT use inheritance from a class not documented for subclassing (Bloch, Effective Java, Item 19).

The split-state anti-pattern (real 211 example)

SP19.Lab11.MyArrayList and W18.Lab9.MyArrayList ship with:

public class MyArrayList<E extends Comparable<? super E>>
       extends ArrayList<E>
       implements Iterable<E>
{
   protected ArrayList<E> list;            // composed backing store

   public MyArrayList() { this.list = new ArrayList<>(); }

   public void addLast(E e) { this.list.add(e); }  // delegates to list
   public int size() { return this.list.size(); }   // shadows ArrayList.size()
}

This class extends ArrayList<E> (inheriting its entire API) AND holds a separate ArrayList<E> list field. Every operation delegates to this.list, leaving the inherited ArrayList state permanently empty. A caller who downcasts to ArrayList<E> and calls the inherited add method writes to the inherited state, not this.list. The class has two stores that diverge silently.

Bloch-corrected form: drop extends ArrayList<E>, implement only Iterable<E>, and use composition exclusively.


Worked Example: Predict Then Check

A student designs Manager extends Employee. Employee has methods getSalary(), getName(), getDepartment(), getAge(), getHireDate(), and five others. Manager needs only getSalary(), getName(), and an additional getTeamSize().

Predict: (a) how many public methods does Manager inherit from Employee? (b) What happens when Employee adds a new public method getPerformanceScore() in a future release?

Reasoning

(a) All of Employee’s public methods are inherited: getSalary, getName, getDepartment, getAge, getHireDate, plus five others = at least 10.

(b) Manager automatically inherits getPerformanceScore(). Even if Manager should not expose that method, callers can now call it on any Manager instance. The subclass has no way to block the inheritance.

Show answer

(a) Manager inherits all public methods of Employee (at least 10 in this example), whether it needs them or not.

(b) getPerformanceScore() is automatically part of Manager’s public interface. If Manager should not have it, inheritance was the wrong choice. Composition (HAS-A Employee field, delegating only getSalary and getName) would have kept full control.

Quick check

Check your understanding

A class needs only two methods from Foo: getLabel() and getCount(). The developer writes ‘class Bar extends Foo’. According to the decision rule in this lesson, what is the immediate consequence?

Tier 2 · BJP (Reges and Stepp), Ch 8


Common Misconceptions

Misconception 1: inheriting to get code reuse

Wrong mental model: “I want my class to have all of Foo’s methods, so I will extends Foo.”

Why it breaks: Inheritance exposes every public method of Foo as part of the new class’s API, permanently. If Foo changes (adds a method, changes behavior of an existing one), the subclass is affected with no recourse. The intent was code reuse, but the result is permanent coupling.

How to correct: If you want Foo’s behavior but not its API surface, hold a Foo field and delegate only the operations you need. This is the “wrapper” or “decorator” pattern.

Source: Bloch, Effective Java, Item 18.


Misconception 2: inheriting from a class not designed for inheritance

Wrong mental model: “Every public class in the JDK is fair game for extends.”

Why it breaks: Classes that are not documented for subclassing make internal implementation assumptions (method A calls method B, which the subclass overrides). Overriding method B changes behavior in unexpected ways. Bloch, Effective Java, Item 19 documents this failure mode with a concrete HashSet example.

How to correct: Use inheritance only from classes whose Javadoc explicitly describes subclassing expectations, or from classes you control yourself. For third-party classes, compose.

Source: Bloch, Effective Java, Item 19.


Misconception 3: extending and composing the same class simultaneously

Wrong mental model: “I will keep a backing field for the internal implementation, but also extend the class so callers can use it polymorphically.”

Why it breaks: The class has two independent backing stores. Every operation must choose which store to use. A caller who accesses the class through the parent type’s interface goes to the inherited store; internal code goes to the composed field. They diverge without warning.

How to correct: Choose one path. If substitutability is required, extend and delete the composed field; use the inherited state. If composition is required, drop extends and implement the desired interface explicitly.

Source: SP19.Lab11.MyArrayList and W18.Lab9.MyArrayList (documented anti-patterns); Bloch, Effective Java, Item 18.


Quick check

Check your understanding

A developer says: ‘I want my Formatter class to have all of Printer’s methods, so I will extend Printer.’ The lesson identifies this as which misconception, and what is the corrective action?

Tier 2 · Bloch, Effective Java, Item 18


Formal Definition and Interface Contract

From Bloch, Effective Java, Item 18:

Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. [...] If there is any doubt, don’t inherit; use composition instead.

It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the same programmers’ control. It is also safe to use inheritance when extending classes specifically designed and documented for extension.


Mental Model

Think of inheritance as welding two objects together at the factory. The seam is permanent, and every feature of one is exposed through the other. Composition is using a socket wrench: you attach one tool to another as needed, expose only the parts relevant to the current job, and switch the tool out at any time. Most engineering problems call for socket wrenches, not welding.


Connections

Within CSCD 210/211: The comparator design uses composition extensively: GamesPlayedComparator HAS-A comparison strategy but does not extend any class. This lesson explains the design principle behind that choice.

Looking back: IS-A Is Inheritance defined IS-A. This lesson defines when NOT to use it.

Looking ahead: The association, aggregation, and composition lessons refine the HAS-A spectrum into three lifetime regimes, giving a more precise vocabulary for composition-based designs.


Practice

Level 1

State Bloch’s rule and the four reasons behind it in bullet form.

Show answer

Rule: Favor composition over inheritance. Use inheritance only when the relationship is genuinely IS-A (substitutability required).

Four reasons:

  1. Encapsulation: composition exposes only what is deliberately delegated.
  2. Loose coupling: the composed type can be swapped; the parent class cannot.
  3. Multiple HAS-A is possible; single class inheritance is the Java limit.
  4. Composition is reversible; extends is permanent coupling.

Level 2

Diagnose the MyArrayList design from SP19.Lab11 (shown in “The Idea” section). What is the specific problem, and what is the corrected form?

Show answer

Problem: MyArrayList extends ArrayList<E> (inheriting 30+ public methods, all of which delegate to the inherited backing state) AND holds a protected ArrayList<E> list field (all internal methods delegate to this field). The two backing stores diverge. Code that calls the inherited size() reads the inherited, always-empty store. Code that calls the overridden size() reads this.list. The class has a split personality.

Corrected form:

public class MyArrayList<E extends Comparable<? super E>>
       implements Iterable<E>
{
    private final ArrayList<E> list = new ArrayList<>();

    public void addLast(E e) { this.list.add(e); }
    public int size()        { return this.list.size(); }

    @Override
    public Iterator<E> iterator() { return this.list.iterator(); }
}

Drop extends ArrayList<E>. Implement only the interfaces the class needs. Expose only the operations that are deliberately supported.


Level 3

A Vehicle class has make, model, and year fields plus getters and compareTo (natural order: by make, then model). A Car class needs make, model, year, doors, and passengerCapacity. Choose composition or inheritance and justify in three sentences.

Show answer

Inheritance is justified here IF a Car truly IS-A Vehicle and substitutability is required: code that takes Vehicle should accept Car. If the domain requires that, class Car extends Vehicle is correct, adding doors and passengerCapacity as new fields.

If no code passes Car where Vehicle is expected, composition is safer: class Car { private final Vehicle vehicle; private int doors; ... }. This avoids inheriting Vehicle’s natural ordering (which may not apply to Car), avoids exposing all of Vehicle’s API on Car, and avoids the permanent coupling to Vehicle’s evolution.

For a typical CSCD 211 lab where both Car and Vehicle are course-defined types and substitutability is expected, extending is acceptable. For a production system with uncertain future requirements, composition gives more flexibility.


Go Deeper (optional)

None of this depth is needed to write correct code today. Read it when you are curious about where the rule comes from.

Why “inheriting from a class not designed for inheritance” is a precise danger, not a vague warning. Bloch’s Item 19 uses InstrumentedHashSet as a concrete failure: the class overrides add to count insertions, but addAll in HashSet delegates to add internally. Count the total insertions after calling addAll(["a","b","c"]) and you get 6, not 3, because each element is counted twice (once by addAll’s internal call to add, once by the override). The class inherits a fragile internal contract that is not part of the documented API and can change in any JDK release. This is not a hypothetical; it is a documented failure mode in a real JDK class.

Where this rule sits in design history. The “favor composition over inheritance” principle is not Bloch’s invention. David Parnas’s 1972 paper on information hiding argued that a module should expose only the decisions others need and hide the rest. The Gang of Four (Design Patterns, 1994) named it as a general principle and showed it in the Strategy pattern, where the varying behavior is held as a composed object rather than baked into an inheritance hierarchy. The “wrapper” form Bloch recommends is the Decorator pattern from that same book.

The connection to type theory and design patterns. Inheritance maps to the IS-A (subtype) relationship. Composition maps to HAS-A (product type). A Comparator<T> is a composed comparison strategy, which is the Strategy pattern: the algorithm varies independently of the object that uses it. When you hold a Comparator<T> field rather than extending a class that happens to sort in the order you want, you are applying both the “favor composition” rule and the Strategy pattern simultaneously.

What this means in professional Java. Production Java service classes almost never use class inheritance beyond Object. A typical Spring or Jakarta EE service class has fields of repository, service, and utility types, implements one or two interfaces, and extends nothing. If you open a large real-world Java codebase and search for extends, most results will be interface implementations (implements) or framework-mandated base classes. The rare use of class inheritance in production is a signal, not a default.


Check Yourself

Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.

Check your understanding

According to Bloch’s rule stated in this lesson, when is inheritance the right choice?

Tier 1 · BJP (Reges and Stepp), Ch 8

The lesson lists four reasons to prefer composition over inheritance. Which of the following is NOT one of those four reasons?

Tier 1 · BJP (Reges and Stepp), Ch 8

In the SP19.Lab11 MyArrayList example, what is the specific problem caused by extending ArrayList AND holding a separate ArrayList field?

Tier 2 · BJP (Reges and Stepp), Ch 8

Predict what happens when the following client code runs against the split-state MyArrayList from SP19.Lab11: MyArrayList<String> m = new MyArrayList<>(); m.addLast(“alpha”); m.addLast(“beta”); System.out.println(m.size()); // line A ArrayList<String> raw = m; // upcast via extends System.out.println(raw.size()); // line B

Tier 2 · BJP (Reges and Stepp), Ch 8

A teammate writes: ‘I will extend LogEngine so my ReportWriter class can call its formatEntry and flushBuffer methods.’ According to the misconceptions section, what is the best diagnosis of this design?

Tier 3 · BJP (Reges and Stepp), Ch 8