How do overrides work in deep class hierarchies?

← Override Mechanics · Ref: Q575

In a deep hierarchy (grandparent, parent, child), each level that replaces a method must use 'override'. The keyword is required at every level that provides a new implementation.

OVERRIDE WORKS ON BOTH METHODS AND OPERATORS

The override keyword applies to methods AND operators:

  override describe() as pure      method override
  override operator ? as pure      operator override

Both follow the same rule: if the parent defines it, you must say override.

THREE-LEVEL PATTERN

Grandparent declares the method. Parent overrides it (must use 'override'). Grandchild overrides again (must use 'override'). Each level independently declares its intent to replace.

EVERY LEVEL NEEDS OVERRIDE

Even though grandparent is the original, and parent already overrides, the grandchild must ALSO say 'override'. There is no implicit inheritance of the override status. This applies equally to methods and operators.

SUPER ACCESS

Each level can access the immediate parent with 'super':

  override describe()
    parentDesc <- super.describe()
    <- rtn as String: parentDesc + " + child"

SKIPPING LEVELS

If the parent does NOT override (keeps grandparent behavior), the grandchild can still override the grandparent method. The override applies to whatever implementation was last provided.

See Q570 for override basics. See Q567 for purity through chains. See Q102 for 'as open'.

Example

defines module qa.override.deepchain

  defines class

    //Level 1: grandparent — operator ? needs override (inherited from base)
    Transport as open
      describe() as pure
        <- rtn as String: "transport"

      capacity() as pure
        <- rtn as Integer: 0

      //override on operator — same keyword as methods
      override operator ? as pure
        <- rtn as Boolean: true

      operator $ as pure
        <- rtn as String: `Transport: ${describe()}`

    //Level 2: parent overrides methods AND operators
    LandTransport extends Transport as open
      override describe() as pure
        <- rtn as String: "land transport"

      override capacity() as pure
        <- rtn as Integer: 4

      //override on operator $ — parent defined it, so override required
      override operator $ as pure
        <- rtn as String: `Land: ${describe()}`

    //Level 3: grandchild overrides again — methods and operators
    Truck extends LandTransport
      override describe() as pure
        <- rtn as String: "truck"

      override capacity() as pure
        <- rtn as Integer: 20

      override operator $ as pure
        <- rtn as String: `Truck: ${describe()}`

    //Another branch at level 3
    Motorcycle extends LandTransport
      override describe() as pure
        <- rtn as String: "motorcycle"

      override capacity() as pure
        <- rtn as Integer: 2

      override operator $ as pure
        <- rtn as String: `Bike: ${describe()}`

  defines program

    DeepOverrideChainDemo()
      stdout <- Stdout()

      //Each level has its own implementation
      items <- List() of Transport
      items += Transport()
      items += LandTransport()
      items += Truck()
      items += Motorcycle()

      for item in items
        stdout.println(`${item.describe()}: capacity ${item.capacity()}`)

Common mistakes

E05120 — At every level of the hierarchy, the override keyword is required when replacing a parent method. Omitting it at any level triggers shadowing detection. See ek9 -h E05120 for details.

Incorrect:

describe() as pure

Correct:

override describe() as pure

E05030 — If LandTransport extended Truck which extends LandTransport, a circular hierarchy would be created. The compiler detects cycles at any depth. See ek9 -h E05030 for details.

Incorrect:

LandTransport extends Truck as open

Correct:

LandTransport extends Transport as open
Other ways to ask this
  • Does override apply to grandparent methods?
  • How does a three-level override chain work?
  • Must each level use 'override' in a deep hierarchy?

Coming from another language?

Java: @Override at each level is recommended but optional. Kotlin: override at each level is mandatory. C#: override at each level is required. EK9: override is mandatory at every level in the chain.

Keywords: child, inherit, override, parent, open, grandparent, virtual, class, abstract, level, deep, hierarchy, chain