Type Hierarchy and Dispatcher

Type Hierarchy reads what a type extends and implements, and what extends it in turn; Dispatcher reads which concrete type resolves to which handler. Here Notification is an abstract base with three subtypes - EmailNotification, SmsNotification, PushNotification - and EmailNotification alone implements the Loggable trait. NotificationRouter dispatches on Notification, with handlers for only two of the three. To play it in the IDE: Help > Lessons > Understanding a codebase…, or ek9 -ide -lesson type-hierarchy.

The EK9 IDE as the lesson opens

Start from

The code the lesson starts from: copy it into a file of your own and follow the steps.

notifications.ek9

#!ek9
<!-
  The shared project for the 'type-hierarchy' and 'call-graph' Analyse lessons: a small abstract
  hierarchy (Notification), a trait one subtype implements (Loggable), a dispatcher
  (NotificationRouter) with handlers for two of the three subtypes - the third falls back to the
  entry point's own body - and a function that reads any Notification polymorphically, so Type
  Hierarchy, Dispatcher, Call Graph and Neighbourhood each have something real to show.
-!>
defines module lesson.relationships

  defines trait

    Loggable
      logTag() as pure
        <- rtn as String: "LOG"

  defines class

    Notification as abstract
      summary() as abstract
        <- rtn as String?

      default operator ?

    EmailNotification extends Notification with trait of Loggable

      override summary()
        <- rtn as String: `${logTag()}: emailing the customer`

      default operator ?

    SmsNotification extends Notification

      override summary()
        <- rtn as String: "texting the customer"

      default operator ?

    PushNotification extends Notification

      override summary()
        <- rtn as String: "pushing to the app"

      default operator ?

    NotificationRouter

      route() as dispatcher
        -> n as Notification
        <- rtn as String: "unrouted"

      private route()
        -> n as EmailNotification
        <- rtn as String: `sending by email: ${n.summary()}`

      private route()
        -> n as SmsNotification
        <- rtn as String: `sending by sms: ${n.summary()}`

  defines function

    describeNotification()
      -> n as Notification
      <- rtn as String: `notice: ${n.summary()}`

//EOF

delivery.ek9

#!ek9
<!-
  The second file of the shared 'relationships' project: functions that call across both files -
  buildNotifications constructs the three subtypes, deliverAll calls describeNotification
  (notifications.ek9) and NotificationRouter's dispatcher for each one.
-!>
defines module lesson.relationships

  defines function

    buildNotifications()
      <- rtn as List of Notification: List() of Notification
      rtn += EmailNotification()
      rtn += SmsNotification()
      rtn += PushNotification()

    deliverAll()
      -> notifications as List of Notification
      stdout <- Stdout()
      router <- NotificationRouter()
      for n in notifications
        stdout.println(describeNotification(n))
        stdout.println(router.route(n))

  defines program

    Deliver()
      deliverAll(buildNotifications())

//EOF

Step 1: A subtype's own hierarchy

Put the caret on EmailNotification, in notifications.ek9, then choose Analyse > Type Hierarchy.

The EK9 IDE once this is done: Put the caret on EmailNotification, in notifications.ek9, then choose Analyse > Type Hierarchy

Step 2: Reading the diagram

EmailNotification sits in the middle with a thicker border; Notification shows above it, joined by a solid grey line - extends; Loggable shows to its right, joined by a dashed teal line - implements. Every box's border colour is its genus: blue for a class, teal for a trait. Click a box's own name to jump to its source.

The EK9 IDE once this is done: EmailNotification sits in the middle with a thicker border; Notification shows above it, joined by a solid grey line - extends; Loggable shows to its right, joined by a dashed teal line - implements

Step 3: From the base, looking down

Put the caret on Notification instead, then choose Analyse > Type Hierarchy again - the same menu item, reading whatever construct the caret sits on now. With the caret on the base itself, the diagram turns round: Notification sits at the top this time, with all three subtypes below it, each joined to it the same way EmailNotification was.

The EK9 IDE once this is done: Put the caret on Notification instead, then choose Analyse > Type Hierarchy again - the same menu item, reading whatever construct the caret sits on now

Step 4: Who handles each type

Put the caret on route() as dispatcher, in NotificationRouter, then choose Analyse > Dispatcher.

The EK9 IDE once this is done: Put the caret on route() as dispatcher, in NotificationRouter, then choose Analyse > Dispatcher

Step 5: Reading the resolution table

The resolution table lists all three concrete types route() could be called with. EmailNotification and SmsNotification show a green checkmark - DIRECT - each with an arrow to its own handler; PushNotification shows a red warning - FALLBACK - routed to route()'s own entry-point body instead, since nothing handles it directly. The badges below count it: Types: 3, Direct: 2, Gaps: 1.

The EK9 IDE once this is done: The resolution table lists all three concrete types route() could be called with

The result

That is Type Hierarchy and Dispatcher: EmailNotification's own view showed Notification above it and Loggable beside it; Notification's own view showed all three subtypes below it. NotificationRouter's dispatcher resolves EmailNotification and SmsNotification directly to their own handlers, and falls back to its entry point's own body for PushNotification, which has none - the gap a non-exhaustive dispatcher leaves. Analyse > Type Hierarchy (⇧⌘T) and Analyse > Dispatcher (⇧⌘D) read the caret's own construct; AI assistants get the same data from ek9_query_type_hierarchy and ek9_query_dispatchers.

Where this lesson comes from