How do I make a class extensible with 'as open'?
← Classes and OOP · Ref: Q102
In EK9, use 'as open' to allow a class to be extended. Without it, classes are closed by default (E05030). Abstract classes are automatically open since they must be extended to be useful.
THE AS OPEN MODIFIER
Mark a class as open to allow subclasses:
Shape as open name <- String()
Now other classes can extend Shape. Without 'as open', attempting to extend Shape triggers E05030.
WHICH CONSTRUCTS SUPPORT AS OPEN?
Five constructs support 'as open': functions, records, classes, components, and traits. Traits are inherently open (designed to be implemented) so 'as open' on a trait is accepted for syntax consistency but has no additional effect. Services and text blocks cannot be extended.
ABSTRACT CLASSES ARE AUTOMATICALLY OPEN
Abstract classes are inherently open because they require concrete subclasses:
Vehicle as abstract speed() as abstract <- rtn as Float?
'as abstract' implies 'as open' — you never need both.
INHERITANCE SYNTAX
Use 'extends' or 'is' to inherit:
Circle extends Shape Circle is Shape
Both are equivalent. Use 'override' for overridden methods — it is mandatory in EK9, not optional like in Java.
OVERRIDE IS MANDATORY
Unlike Java where @Override is an annotation you can forget, EK9 requires the 'override' keyword on any method that overrides a parent method. Omitting it triggers E05120. This prevents accidental shadowing.
SEALED ALTERNATIVE: ALLOW ONLY
If you want limited extensibility rather than fully open, use 'allow only' to restrict which classes can extend:
Shape allow only Circle, Square, Triangle as open
This is EK9's equivalent of Java 17's sealed classes. See Q298-Q301 for details.
WHEN TO USE AS OPEN
Use 'as open' when you deliberately design a type for extension. Keep open types small in number — most types should remain closed. Prefer composition (Q109) and traits (Q106) over inheritance where possible.
See Q101 for why types are closed by default. See Q103 for abstract classes. See Q93 for class basics. See Q106 for traits. See Q570 for override mechanics in open class hierarchies. See Q298 for sealed traits with 'allow only'. See Q301 for sealed classes.
Example
defines module qa.oop.asopen defines class Shape as open name <- "shape" Shape() -> name as String this.name: name name() as pure <- rtn as String: name describe() <- rtn as String: "I am a " + name default operator Circle extends Shape radius <- 0.0 Circle() -> radius as Float super("circle") this.radius: radius radius() as pure <- rtn as Float: radius override describe() <- rtn as String: `I am a circle with radius ${radius}` default operator ? Vehicle as abstract speed() as abstract <- rtn as Float? describe() <- rtn as String: `Vehicle at ${speed()} mph` default operator ? Car is Vehicle topSpeed <- 120.0 Car() -> topSpeed as Float this.topSpeed: topSpeed override speed() <- rtn as Float: topSpeed default operator ? defines record Coordinate xPos as Float: 0.0 yPos as Float: 0.0 default operator ? defines program AsOpenDemo() stdout <- Stdout() // === AS OPEN: explicit extension === shape <- Shape("triangle") stdout.println(shape.describe()) circle <- Circle(5.0) stdout.println(circle.describe()) // === ABSTRACT: automatically open === car <- Car(150.0) stdout.println(car.describe()) // === OVERRIDE === stdout.println(`Circle name: ${circle.name()}`) stdout.println(`Car speed: ${car.speed()}`)
Common mistakes
E05120 — When overriding a method from a parent class, the 'override' keyword is mandatory. Omitting it on Circle's describe() triggers E05120 because the method exists in Shape. See ek9 -h E05120 for details.
Incorrect:
describe()
Correct:
override describe()
E05030 — Without 'as open', Shape would be closed by default and Circle could not extend it. Attempting to extend a closed class triggers E05030 — not open to be extended. EK9 types are closed by default to prevent the fragile base class problem. See ek9 -h E05030 for details.
Incorrect:
Shape
Correct:
Shape as open
E05120 — Car must use 'override' when implementing the abstract method speed() from Vehicle. Omitting 'override' triggers E05120. See ek9 -h E05120 for details.
Incorrect:
speed()
Correct:
override speed()
E50020 — A class defined in 'defines class' cannot extend a record from 'defines record' because they are different construct types (genus). Classes extend classes, records extend records. See ek9 -h E50020 for details.
Incorrect:
Circle extends Coordinate
Correct:
Circle extends Shape
E50060 — Circle's constructor calls super("circle") which expects Shape's single-String constructor. Vehicle has a different constructor signature (make, year). Extending Vehicle with Shape's super call triggers E50060 — constructor not resolved. See ek9 -h E50060 for details.
Incorrect:
Circle extends Vehicle
Correct:
Circle extends Shape
Other ways to ask this
- How do I allow a class to be extended in EK9?
- What does 'as open' mean on a class?
- How does inheritance work in EK9?
Coming from another language?
Java: all classes are open by default, use 'final' to close; @Override annotation is optional (easily forgotten); sealed classes added in Java 17 with 'permits'. Python: all classes open, no way to seal. Rust: no inheritance at all, traits only. Go: no inheritance, embedding only. Kotlin: classes final by default, 'open' keyword to allow extension (same approach as EK9), 'override' keyword mandatory (same as EK9). Swift: classes not final by default but 'final' keyword available. EK9: closed by default, 'as open' to allow extension, 'as abstract' is automatically open, 'extends' or 'is' for inheritance, 'override' mandatory not optional, 'allow only' for sealed types.
Keywords: function, modifier, child, object-oriented, abstract, record, extensible, only, extends, component, sealed, is, parent, allow, override, inherit, super, base, open, class, define, virtual, inheritance, subclass