References for our discussion:
- The power of abstraction, reuse and simplicity: an object-oriented library for event-driven design. Most of the code implemented for a EDA is derived from this document.
- Graphic Objects. Lisp example for EDA. Specifically Graphic User Interfaces.
- Big, Complex, and Tested? Just Say ‘When’. Ruby example. Follows closely Bertrand Meyer’s document for the implementation of the
Requirements for our EDA:
- Storage of events to be executed when events triggered.
- Events.
- Push/Pull Model.
- Broadcast of events. One trigger can execute more than one handler.
- Ex. When User Clicks Save Button, data is saved and the event is logged as executed.
The storage mechanism is nothing more than hash-table.
(defobj handler-table (:members ((events (make-hash-table)))) (:methods ((:can-fire (event) (let ((handler (make-events-handler))) (if (null (gethash event events)) (setf (gethash event events) handler)))) (:when-fire (event expression) (let ((handler (gethash event events))) (subscribe handler expression) (setf (gethash event events) handler))) (:listeners? (event) (let ((handler (gethash event events))) (if (eq (h-count handler) 0) nil t))) (:fire (event &optional args) (let ((handler (gethash event events))) (cond (args (trigger handler args)) (t (trigger handler)))))))
The methods utilized by the table:
- can-fire
- This creates a hash for the event name.
- when-fire
- This subscribes the event.
- listeners?
- Asks if there are any functions stored in events-handler (list of events).
- fire
- Triggers all events in the events list.
Here is the unit tests:
(test-fixture handler-table (:setup ((handlers (make-handler-table)))) (:tests (should-have-zero-listeners (can-fire handlers :whatever-event) (assert-false (listeners? handlers :whatever-event))) (should-not-make-new-events-handler-or-overwrite-events-handler (can-fire handlers :whatever-event) (when-fire handlers :whatever-event (lambda () (+ 1 1))) (can-fire handlers :whatever-event) (assert-true (listeners? handlers :whatever-event))) (should-trigger-events (can-fire handlers :whatever-event) (assert-false (listeners? handlers :whatever-event)) (when-fire handlers :whatever-event (lambda () (+ 1 1))) (assert-equal t (listeners? handlers :whatever-event)) (assert-equal 1 (fire handlers :whatever-event)) (when-fire handlers :whatever-event (lambda () (+ 1 20))) (assert-equal 2 (fire handlers :whatever-event))) (should-fire-push-model-events (can-fire handlers :whatever-event) (when-fire handlers :whatever-event (lambda (x) (+ x 1))) (assert-equal 1 (fire handlers :whatever-event 21))))
The items stored in the table are lists of closures.
The events list:
(defobj events-handler (:members ((events (make-list 0)))) (:methods ((:h-count () (length events)) (:subscribe (event) (cond ((functionp event) (push event events)))) (:trigger (&optional args) (let ((trigger-count 0)) (dolist (event events) (cond (args (funcall event args)) (t (funcall event))) (incf trigger-count)) trigger-count))))
The list utilizes several methods.
- h-count
- Returns the number events in the list.
- subscribe (event)
- Stores the event (closure/function). It verifies that the event is actually a function before storing it.
- trigger (&optional args)
- Iterates and executes each function stored in list. It returns the actual number of functions triggered.
Here are the unit tests:
(test-fixture events (:setup ((handler (make-events-handler)))) (:tests (should-retrieve-handlers-count-of-zero (assert-equal 0 (h-count handler))) (should-not-add-handler (subscribe handler :nothing) (assert-equal 0 (h-count handler))) (should-add-handler (subscribe handler (lambda () 1)) (assert-equal 1 (h-count handler)) (subscribe handler (lambda () 1)) (assert-equal 2 (h-count handler))) (should-add-push-model-handler (subscribe handler (lambda (x) x)) (assert-equal 1 (h-count handler))) (should-not-trigger-with-no-events (assert-equal 0 (trigger handler))) (should-trigger-push-model (subscribe handler (lambda (x) x)) (assert-equal 1 (trigger handler 1))) (should-trigger-two-events-in-handler (subscribe handler (lambda () 1)) (subscribe handler (lambda () 1)) (assert-equal 2 (h-count handler)) (assert-equal 2 (trigger handler))))
Happy Days!
[...] Event Driven Architecture Programming in Lisp Part 2 [...]