Inheritance: Extend a Compatible Contract

Specialize a school notice without changing its promise

A report displays different notice objects by asking each for text. An urgent notice should still provide text, while specializing the message it returns.

Inheritance lets a subclass reuse and specialize behavior from a parent class. A useful child remains compatible with the parent’s promised interface.

  1. Read the relationship

    Identify parent and subclass.

  2. Trace an override

    Explain which implementation a call uses.

  3. Compare composition

    Recognize when receiving a helper is more flexible than subclassing.

An urgent notice keeps the text contract

  1. Parent contract

    A notice supplies a string message.

    text()
  2. Specialized class

    An urgent notice changes the wording.

    override text()
  3. Caller

    Use the same operation on the specialized object.

    Urgent: Library closed
Read these stages alongside the complete example. They explain the relationship; they do not execute code.

A child must be usable where the parent is promised

JavaScript extends declares the parent relationship. A method with the same name can override the inherited implementation. The caller invokes the receiver’s applicable implementation.

Inheritance is not just a device to avoid copying lines. If a child changes accepted inputs or result meaning incompatibly, it can break callers expecting the parent’s contract. Favor shallow, understandable relationships.

Before starting, review classes and methods. These examples use JavaScript; other languages can use different syntax and rules.

Use both notices through one operation

The ordinary instance returns Notice. The specialized instance returns the urgent message because its method overrides the parent method.

Both return strings, so the report can display them with the same call pattern. JavaScript does not require a formal interface declaration for this example.

JavaScript · local teaching example

class Notice {
  text() { return 'Notice'; }
}
class UrgentNotice extends Notice {
  text() { return 'Urgent: Library closed'; }
}
const notices = [new Notice(), new UrgentNotice()];
for (const notice of notices) console.log(notice.text());

Expected output

Notice
Urgent: Library closed

A helper can supply behavior without a subclass

Composition combines objects or functions as collaborators. Here the notice receives the message-producing function rather than inheriting a separate class for every wording.

This can make independent choices easier to combine. It still needs a clear collaborator contract; dependency injection is not automatic correctness.

JavaScript · local teaching example

class Notice {
  constructor(message) { this.message = message; }
  text() { return this.message(); }
}
const notice = new Notice(() => 'Library opens at 9');
console.log(notice.text());

Expected output

Library opens at 9

Apply the rule to a small change

Create a ReminderNotice subclass returning “Bring your library card” and call its text method.

Worked answer

The reminder changes the message while preserving the string-returning text operation. It does not need to override unrelated behavior.

JavaScript · local teaching example

class Notice { text() { return 'Notice'; } }
class ReminderNotice extends Notice {
  text() { return 'Bring your library card'; }
}
console.log(new ReminderNotice().text());

Expected output

Bring your library card

Recognize mistakes and limits

A subclass constructor must call super before using this in JavaScript. That initializes the parent portion of the instance. Do not duplicate parent initialization blindly.

Avoid deep trees that encode unrelated dimensions, such as urgent plus emailed plus translated. Composition can combine independent features more clearly. If a subtype cannot honor the parent contract, reconsider the relationship.

For the language or tool rules used here, see JavaScript class behavior.

Check the core idea

JavaScript · code for this question

class Notice {
  text() { return 'Notice'; }
}
class UrgentNotice extends Notice {
  text() { return 'Urgent: Library closed'; }
}
const notices = [new Notice(), new UrgentNotice()];
for (const notice of notices) console.log(notice.text());
Why does the urgent instance print its specialized text?
Choose one answer. You can try again.

Answer and explanation

Its text method overrides the inherited implementation.

Yes. The receiver uses the specialized method.

Apply the idea to a new situation

When is composition worth considering?
Choose one answer. You can try again.

Answer and explanation

When behaviors vary independently and helpers can be combined.

Yes. It can avoid a large hierarchy of feature combinations.

Extend a promise, not just a code fragment

Inheritance specializes compatible behavior. Trace overrides, preserve input and result contracts, and compare composition when independent features need combining.

Connect this idea with common operations with different implementations and composition and responsibility.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Polymorphism: One Operation, Different Implementations →