Review this design. Should it use composition instead of inheritance?

← Code Quality · Ref: Q1045

In EK9, composition is generally preferred over inheritance. Types are closed by default — you must explicitly opt into extension with 'as open'.

WHEN INHERITANCE IS APPROPRIATE:

- True 'is-a' relationship (Circle is a Shape)
- Shared behaviour that varies by subtype (abstract methods)
- Small, stable hierarchies (2-3 levels max)

WHEN COMPOSITION IS BETTER:

- 'Has-a' relationship (Car has an Engine, not Car is an Engine)
- Combining capabilities from multiple sources (use traits)
- When the parent class might change (composition isolates you)
- When you need to swap implementations at runtime

EK9 COMPOSITION TOOLS:

- Traits for shared contracts: 'with trait of Printable'
- Delegation with 'by' keyword
- Records for pure data aggregation
- Components for DI-managed services

The compiler enforces max inheritance depth — if you hit the limit, it is a signal to refactor toward composition.

Example

defines module qa.codequality.reviewcomposition

  defines trait

    Describable
      describe() as pure
        <- rtn as String?

  defines class

    Engine
      horsePower as Integer: 0

      Engine()
        -> horsePower as Integer
        this.horsePower :=: horsePower

      horsePower() as pure
        <- rtn as Integer: horsePower

      default operator

    //Composition: Car HAS an Engine (correct)
    Car with trait of Describable
      engine as Engine: Engine(0)
      modelName as String: String()

      Car()
        ->
          modelName as String
          engine as Engine
        this.modelName :=: modelName
        this.engine :=: engine

      override describe() as pure
        <- rtn as String: `${modelName} (${engine.horsePower()}hp)`

      default operator

  defines program

    CompositionDemo()
      stdout <- Stdout()

      engine <- Engine(250)
      car <- Car("Roadster", engine)
      stdout.println(car.describe())

Common mistakes

E05030 — Car is not an Engine — it has an Engine. Use composition (field) not inheritance (extends). Also, Engine must be 'as open' to extend.

Incorrect:

      defines class
        Car extends Engine

Correct:

  defines class

    Engine
Other ways to ask this
  • Is inheritance the right choice here or should I use composition?
  • Assess whether this class hierarchy is appropriate
  • When should I use composition over inheritance in EK9?

Coming from another language?

EK9 encourages composition through closed types, traits, and delegation. Inheritance is available but not the default approach.

Keywords: trait, inheritance, composition, delegation, review, design