M7 — Inheritance, Subtyping, and Refactoring

SENG 365 — Software Engineering

Neil Ernst

University of Victoria

2026-10-21

Module Goals

Design Judgment

By the end of this module, you should be able to:

  • separate code reuse from substitutability;
  • test a proposed subtype against its base-type contract;
  • compare Template Method with composition;
  • protect behavior with a characterization test; and
  • connect a refactoring to the design force it addresses.

Inheritance and Subtypes

Preparation

Before class: Read Chapter 7, Sections 7.1–7.6.

Entry Decision

class Stack<E> extends ArrayList<E> {
    void push(E value) { add(value); }
    E pop() { return removeLast(); }
}

The subclass reuses code. Is every ArrayList operation meaningful for a Stack?

Base-Type Expectations

A client receiving List<E> may reasonably call:

items.add(0, value);
items.remove(3);
items.sort(comparator);

If a Stack restricts or changes those operations, can it safely stand in for the base type?

Subtype Contract Check

For the proposed Stack extends ArrayList relation:

  1. list what clients of the base type may assume;
  2. identify any inherited operations that violate the stack abstraction;
  3. propose a composition-based alternative.

Does inheritance help reuse?

Dispatch and Static Types

Employee employee = new Manager();
employee.calculatePay();
  • The compile-time type controls which members can be requested.
  • The run-time type selects the most specific overridden method.
  • Downcasting usually signals that the abstraction available to the client is insufficient.

Substitutability

Inheritance Design Rules

  • Keep inherited fields private where possible.
  • A subclass should complement or specialize base behavior.
  • Do not use inheritance to remove valid base operations.
  • Mark methods or classes final when extension is not part of the design.
  • Avoid overloading where related parameter types make selection surprising.

Exit Note

The relation is syntactically valid but conceptually false because __________.

Template Method or Composition?

Preparation

Before class: Read Chapter 7, Sections 7.7–7.10.

Entry Decision

A report pipeline always loads data, validates it, formats it, and publishes it. Design pressure: Validation and formatting vary.

Which steps belong to the stable algorithm? Which variations might need to change at run time?

Template Method Sketch

abstract class ReportJob {
    final void run() {
        Data data = load();
        validate(data);
        String report = format(data);
        publish(report);
    }

    abstract void validate(Data data);
    abstract String format(Data data);
}

The base class owns the algorithm. Subclasses fill selected steps.

Composition Sketch

final class ReportJob {
    ReportJob(Validator validator, Formatter formatter) { /* ... */ }
    void run() { /* stable sequence */ }
}

The job delegates variation to injected collaborators.

Compare Both Designs

Produce a class diagram for each design and defend one under these changes:

  • choose formatting at run time;
  • share validation across unrelated jobs;
  • guarantee that publishing always happens last.

Tradeoffs

Template Method can make a stable sequence explicit and prevent reordering. It also couples variants to a base class.

Composition makes variation independently replaceable and testable. It introduces more objects and must preserve orchestration invariants another way.

Exit Note

The clean variation point is __________ because __________ is expected to change.

Smells and Refactoring

Preparation

Before class: Read Section 7.11 and review Chapter 5.

Code smells and named refactorings are supplementary material used to practice the chapter’s design advice.

Entry Decision

Money price(Order order) {
    if (order.kind() == STANDARD) { /* 12 lines */ }
    else if (order.kind() == STUDENT) { /* similar 14 lines */ }
    else if (order.kind() == STAFF) { /* similar 13 lines */ }
    // tax, logging, and receipt formatting follow
}

Which smell matters because of the next change, not merely because the method is long?

Behavior Before Structure

Before changing the code, capture what it currently does:

@Test
void studentOrderIncludesExistingDiscountAndTax() {
    Order order = existingStudentOrder();
    assertEquals(new Money("18.90"), price(order));
}

A characterization test records current observable behavior, including behavior that may not yet be well explained.

Refactoring Studio

The next release adds alumni pricing and a second tax jurisdiction.

Produce:

  1. one characterization test needed before editing;
  2. a refactoring;
  3. the smell or dependency each step addresses; and
  4. the point at which tests should run.

Consider Rename, Extract Method, Introduce Parameter Object, or replacing a conditional with polymorphism, but only where justified.

Code Review as Design Practice

  • Does behavior remain stable?
  • Is each step small enough to explain?
  • Did the change improve the expected change path?
  • Did a new abstraction earn its name and cost?
  • Can an automated check enforce part of the review?

The review comment should name evidence and consequence, not personal preference.

Refactoring Discipline

  • Refactoring changes structure while preserving observable behavior.
  • Smells are heuristics, not defects by definition.
  • A new requirement supplies the design pressure.
  • Tests reduce risk but do not prove equivalence.
  • Separate cleanup from intentional behavior change where possible.

Exit Note

The refactoring __________ should wait until a test captures __________.