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

#!ek9
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: cost based, deep hierarchy, dispatcher, exact match, fallback, inheritance, open, walk up