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