Create me a dispatcher for a deep class hierarchy (Shape -> Polygon -> Quadrilateral -> Square) where the Polygon handler catches Quadrilateral but a more specific Square handler exists.

← Dispatcher Validation · Ref: Q1244

Dispatchers select the handler whose parameter type most closely matches the runtime type of the argument, walking up the inheritance chain until a handler is found. This is COST-BASED dispatch — exact match has cost 0, parent has cost 1, grandparent cost 2, and so on.

DEEP INHERITANCE

  Shape as abstract
    default operator ?
  Polygon is Shape as open
    default operator ?
  Quadrilateral is Polygon as open
    default operator ?
  Square is Quadrilateral
    default operator ?

Note 'as open' on intermediate classes — without it, EK9 closes the class and you cannot extend it further. Square is closed (default) because it is a leaf.

DISPATCHER WITH PARTIAL HANDLERS

Three handlers cover four types in the hierarchy:

  GeometryRenderer
    render() as dispatcher
      -> shape as Shape
      <- rtn as String: "Shape handler"
    render()
      -> shape as Polygon
      <- rtn as String: "Polygon handler"
    render()
      -> shape as Square
      <- rtn as String: "Square handler"

DISPATCH BEHAVIOUR

  renderer.render(Polygon())        // "Polygon handler" (exact match)
  renderer.render(Quadrilateral())  // "Polygon handler" (Quadrilateral has no handler, walks up to Polygon)
  renderer.render(Square())         // "Square handler" (exact match wins over Polygon)

KEY INSIGHT

For Quadrilateral, the dispatcher walks up the hierarchy: no Quadrilateral handler -> try Polygon (found, use it). For Square, the exact handler exists so it wins even though Polygon is also a valid match (lower cost wins).

This avoids the visitor pattern boilerplate found in Java/C#: you only need handlers for the types you actually want to differentiate.

See Q620 for hierarchy dispatch rules. See Q616 for ambiguity detection. See Q1236 for sealed exhaustive dispatch.

Example

defines module qa.dispatcher.deephierarchy

  defines class

    Shape as abstract
      default operator ?

    Polygon is Shape as open
      default operator ?

    Quadrilateral is Polygon as open
      default operator ?

    Square is Quadrilateral
      default operator ?

    GeometryRenderer

      render() as dispatcher
        -> shape as Shape
        <- rtn as String: "Shape handler"

      render()
        -> shape as Polygon
        <- rtn as String: "Polygon handler"

      render()
        -> shape as Square
        <- rtn as String: "Square handler"

  defines program

    DeepHierarchyDispatchDemo()
      stdout <- Stdout()
      renderer <- GeometryRenderer()

      stdout.println(renderer.render(Polygon()))
      stdout.println(renderer.render(Quadrilateral()))
      stdout.println(renderer.render(Square()))

Common mistakes

E05030 — EK9 classes are CLOSED by default. To allow further extension (Quadrilateral extending Polygon), declare the parent 'as open'. Leaf classes can stay closed. See ek9 -h E05030 for details.

Incorrect:

Polygon is Shape

Correct:

Polygon is Shape as open
Other ways to ask this
  • Show me how cost-based dispatch picks the closest handler in a deep inheritance tree.
  • Write a dispatcher that demonstrates exact-match wins over superclass-match.
  • Build a renderer that dispatches across a four-level hierarchy.
  • Implement a dispatcher with handlers for some intermediate types but not all.

Coming from another language?

Java: visitor pattern with accept(visitor) on each subclass — must implement EVERY visit method. Scala: pattern matching on case classes — order matters, exhaustiveness optional. Kotlin: when expression with class checks — must have 'else' or sealed classes. Rust: match on enum variants — exhaustive by default. EK9: dispatcher walks the hierarchy by cost, with explicit fallback in the entry method's body.

Keywords: walk up, fallback, exact match, dispatcher, inheritance, open, cost based, deep hierarchy