How do I implement the observer pattern in EK9?
← Design Patterns and Idioms · Ref: Q262
EK9 implements the observer pattern using abstract functions as listener types and lists of function delegates as subscriber collections. This avoids the interface boilerplate of Java's Observer/Observable.
ABSTRACT FUNCTION AS LISTENER
Define the listener contract as an abstract function:
Listener() as abstract -> event as String
This is the subscription interface. Any function matching this signature can be a listener.
EVENT EMITTER CLASS
Store listeners in a list and iterate to notify:
EventEmitter
listeners as List of Listener
subscribe()
-> listener as Listener
listeners += listener
emit()
-> event as String
for listener in listeners
listener(event)
DYNAMIC FUNCTION SUBSCRIBERS
Create inline listeners with dynamic functions:
printListener <- () is Listener as function Stdout().println(event)
The event parameter comes from the Listener abstract function signature.
CAPTURE FOR CONTEXT
Use explicit capture to add context to listeners:
prefix <- "[AUDIT]" auditListener <- (prefix) is Listener as function Stdout().println(prefix + ": " + event)
The prefix is captured by value at creation time.
ADDING LISTENERS
Subscribe without modifying the emitter:
emitter.subscribe(printListener)
emitter.subscribe(auditListener)
emitter.emit("user-login")
New listeners can be added at any time.
CONSUMER VS ACCEPTOR
Listeners that only read are consumers (pure). Listeners that cause side effects (printing, logging) are acceptors (impure). The abstract function's purity annotation controls this.
See Q54 for consumer and acceptor function types. See Q55 for function delegates. See Q115 for dynamic classes with capture. See Q214 for strategy pattern using similar function callbacks.
Example
defines module qa.patterns.observer defines function Listener() as abstract -> event as String defines class EventEmitter listeners as List of Listener: List() of Listener subscribe() -> listener as Listener listeners += listener emit() -> event as String for listener in listeners listener(event) subscriberCount() as pure <- rtn as Integer: length listeners default operator ? defines program ObserverPatternDemo() stdout <- Stdout() emitter <- EventEmitter() // === DYNAMIC FUNCTION LISTENERS === printListener <- () is Listener as function Stdout().println(`Event received: ${event}`) // Listener with captured context prefix <- "[AUDIT]" auditListener <- (prefix) is Listener as function Stdout().println(`${prefix} ${event}`) // === SUBSCRIBE === emitter.subscribe(printListener) emitter.subscribe(auditListener) stdout.println(`Subscribers: ${emitter.subscriberCount()}`) // === EMIT EVENTS === emitter.emit("user-login") emitter.emit("data-saved") // === ADD MORE LISTENERS LATER === countListener <- () is Listener as function Stdout().println(`Counted: ${event}`) emitter.subscribe(countListener) emitter.emit("final-event")
Common mistakes
E08180 — Class fields must be initialized inline. Declaring a field without an initializer triggers E08180. Always provide an initializer, such as 'List() of Listener' for an empty list. See ek9 -h E08180 for details.
Incorrect:
listeners as List of Listener
Correct:
listeners as List of Listener: List() of Listener
E50001 — Renaming the variable means later references to 'emitter' become unresolved, triggering E50001. Variable names must be consistent throughout the scope. See ek9 -h E50001 for details.
Incorrect:
emitterXYZ <- EventEmitter()
Correct:
emitter <- EventEmitter()
Other ways to ask this
- How do I use events and listeners in EK9?
- How do I implement publish-subscribe in EK9?
- How do function delegates work as event listeners?
Coming from another language?
Java: Observer/Observable (deprecated), listeners via interfaces, anonymous inner classes, lambda expressions since Java 8, EventListener pattern. Python: no built-in observer, callback functions or third-party libraries. JavaScript: EventEmitter (Node.js), addEventListener (DOM), callback-heavy. Rust: no built-in observer, channels or callback closures. Go: channels for pub/sub, callback functions. EK9: abstract functions as listener types, List of Listener for subscriber management, dynamic functions with capture for inline listeners.
Keywords: pattern, idiom, design, migrate, emit, delegate, publish, event, observer, listener, subscribe, callback, notify