What are the design pitfalls of Template Method, and how do calling overridable methods from a constructor or self-use affect a Java abstract-class skeleton?
answer
- Self-use of overridables = published contract (@implSpec)
- Never call overridable methods from a constructor/clone/readObject
- super() runs before subclass fields init
- Fragile base class + class explosion
- Make template final; document or prohibit inheritance
basics
~20 sTemplate Method ties subclasses tightly to the base class, so base changes can break them. A key Java trap: if a constructor (or initializer) calls an overridable method, the subclass's override runs before the subclass's own fields are initialized, leading to bugs. The skeleton's protected methods become a contract you must document and keep stable.
solid answer
~50 sTemplate Method's main hazards are inheritance-related. First, the set of primitive and hook methods becomes a **published contract** for subclassers: which methods are abstract, which are hooks, and how the template calls them ('self-use') must be documented, because changing the call pattern can silently break every subclass — the fragile base class problem. Second, a notorious Java trap: **never call an overridable method from a constructor** (or instance initializer / clone / readObject). The base constructor runs *before* the subclass constructor, so the subclass's override executes while the subclass's fields are still null/default, producing surprising results or NPEs. Third, Template Method causes **class explosion** when variations combine, and makes the skeleton hard to evolve. Mitigations: make the template method `final`, minimize primitives, document self-use, prefer composition (Strategy/injected functions) when variation is dynamic, and consider sealing the hierarchy or using default methods. Effective Java: design and document for inheritance, or prohibit it.
code
java · 17 linesabstract class Base {
Base() { init(); } // BAD: overridable call in constructor
abstract void init();
}
class Sub extends Base {
private String name = "set"; // initialized AFTER super() returns
@Override void init() {
// 'name' is still null here, because Base() ran before this assignment
System.out.println(name.length()); // -> NullPointerException
}
}
// new Sub(); // throws NPE
//
// Fix: don't call overridable methods from constructors. Run the template
// method on a fully constructed object, e.g. var s = new Sub(); s.run();go deeper
Aware that overriding can be risky and that abstract base classes tie subclasses to a parent.
Knows not to call overridable methods from constructors and can explain that base construction precedes subclass field initialization.
Explains the fragile base class problem, self-use documentation, class explosion, and the constructor/clone/readObject hazard with reasoning about init order.
Weighs inheritance vs composition for evolvability, prescribes documenting @implSpec or sealing/finalizing hierarchies, and reasons about API-stability and testability trade-offs across versions.
## Pitfall 1: the abstract-class skeleton is a *published contract* When you write a Template Method base class, its overridable methods (primitives + hooks) and the **order and conditions** in which the template calls them are part of the API that subclassers depend on. This is called **self-use of overridable methods**. If, in a later version, you change *when* or *whether* the template calls a primitive, you can break subclasses that assumed the old pattern — even though you changed nothing in their code. *Effective Java* (Item 19) prescribes: **document precisely the self-use** of overridable methods (the conventional 'Implementation Requirements' / `@implSpec` note), or, if you can't commit to that, **forbid subclassing** (make the class final or its constructors private with static factories). This is the **fragile base class** problem: a safe-looking change to the base ripples into subclasses through the inheritance seam. ## Pitfall 2: calling overridable methods from a constructor (the big Java trap) Java's initialization order is the crux: 1. Subclass constructor implicitly (or explicitly) calls `super(...)` **first**. 2. The **base** constructor body runs — *before* the subclass's field initializers and constructor body. 3. Only then do the subclass's field initializers and constructor body run. If the **base constructor calls an overridable method**, dynamic dispatch sends it to the **subclass's override** — but at that moment the subclass's fields are still at their default values (`null`, `0`, `false`). The override sees an only-half-built object. ```java abstract class Base { Base() { init(); } // calls overridable method in ctor — DANGER abstract void init(); } class Sub extends Base { private String name = "set"; // runs AFTER Base() @Override void init() { System.out.println(name.length()); // name is still null here -> NPE } } new Sub(); // NullPointerException ``` The same trap applies to **instance initializer blocks**, and to `clone()` and `readObject()` (which also act before normal construction completes). **Rule:** never invoke an overridable method from a constructor, initializer, `clone`, or `readObject`. Call only `private`, `static`, or `final` methods there. Template Method's template should be invoked *after* construction, on a fully built object. ## Pitfall 3: inheritance coupling and class explosion - A subclass is **statically bound** to one skeleton; it can't recombine behaviors at run time. - Combinations of variation points multiply subclasses (a subclass per combination) — class explosion. - The base exposes **protected** internals to subclasses, weakening encapsulation and freezing those internals as contract. ## Pitfall 4: testing and reuse friction The variable steps are entangled with the base via `this`, so unit-testing a single step in isolation is harder than with a Strategy object you can mock. Behaviors can't be reused across unrelated algorithms. ## Mitigations and good practice 1. **Make the template method `final`** so subclasses can't corrupt the skeleton. 2. **Minimize primitives**; express optional variation as documented hooks with sane defaults. 3. **Document self-use** (`@implSpec`/Implementation Requirements) — exactly which overridable methods the template calls, when, and what a correct override must guarantee. 4. **Never call overridable methods from constructors/initializers/clone/readObject.** 5. **Prefer composition (Strategy / injected functional interfaces)** when variation is dynamic, numerous, or cross-cutting — favor composition over inheritance. 6. **Seal or finalize** the hierarchy (Java `sealed` classes) when you want a closed, controlled set of subclasses, making the contract analyzable. 7. Consider **interface `default` methods** as a skeleton carrier when you don't want a forced single superclass — though defaults can't access instance state beyond the interface and have their own multiple-inheritance subtleties. ## How the JDK navigates this The skeletal classes (`AbstractList`, etc.) keep primitives tiny (`get`/`size`), document their self-use, don't call overridable methods from constructors, and pair with an interface so you can either extend the skeleton *or* implement the interface directly. They are a model of 'designed and documented for inheritance.'
- Why exactly does an override called from the base constructor see uninitialized subclass fields?Construction runs super() before the subclass's field initializers and constructor body. When the base constructor invokes the overridable method, dispatch goes to the subclass override, but the subclass's fields haven't been assigned yet, so they hold default values (null/0/false).
- What does Effective Java mean by 'design and document for inheritance or else prohibit it'?Either fully specify the class's self-use of overridable methods, provide hooks, test by writing subclasses, and avoid overridable calls in constructors — or make the class final / hide its constructors so it cannot be subclassed at all, removing the fragile-base-class risk.
saying these in an interview costs you the question
- Believing it's safe to call abstract/overridable methods from a constructor because 'the subclass exists' — the subclass's fields aren't initialized yet.
- Treating protected primitives as private implementation you can freely change — they are a contract subclassers rely on.
- Ignoring that making the template non-final lets subclasses break the very invariant the pattern protects.
- Assuming Template Method has no downsides because the JDK uses it; the JDK uses it carefully and documents its self-use.