Programació orientada a esdeveniments
Bucle d'esdeveniments
La programació d'interfícies d'usuari es fa mitjançant esdeveniments. Aquesta és la seqüència:
- L'usuari interactua amb el GUI
- Es produeix un esdeveniment
- En resposta, una peça de codi s'executa
- S'actualitza l'aparença del GUI
Aquestes operacions es produeixen dins del bucle d'esdeveniments (event loop). Els esdeveniments s'afegeixen a una mena de cua, i es van satisfent o gestionant amb el codi que el programador ha decidit. Aquest bucle és un sol fil, i per tant no es poden realitzar operacions massa llargues, ja que es bloquejaria el GUI i deixaria de respondre.
El codi equivalent seria:
do {
e = getNextEvent();
processEvent(e);
} while (e != quit);
El flux d'un programa amb GUI no està predeterminat: depèn dels esdeveniments que es produeixin. En contrast, les aplicacions que es recolzen en algorismes esperen unes dades d'entrada en un ordre i temps predeterminat.
Patró observador
El patró principal que s'utilitza a les interfícies gràfiques és el de l'observador. En aquest patró intervenen una parella subjecte/observador. El funcionament bàsic és que tenim un subjecte que genera esdeveniments i un o més observadors que els escolten. Això ens permet fer push dels esdeveniments, en lloc de fer polling. O sigui, comunicar-los quan passen, en lloc d'haver de comprovar si han ocorregut cada cert temps.
Un patró germà és el publish-subscribe, on parlem de missatges en lloc d'esdeveniments. Tenim publicadors que generen missatges, i els subscriptors interessats es registren i els reben. Aquest patró també es relaciona amb les cues de missatges, habitualment utilitzades conjuntament.
Implementació de les notificacions
Quan l'esdeveniment succeeix, el subjecte acaba notificant a tots els observadors amb una crida al un mètode anomenat update(...). Aquesta notificació o update pot implementar-se de diverses maneres:
- El mètode
update(...)
dels observadors pot tenir diversos paràmetres per a indicar a l'observador quin esdeveniment s'ha produït. En el nostre exemple, un paràmetre amb l'esdeveniment anomenatEvent
. - El mètode pot dir-se de moltes maneres. Per exemple,
onEvent()
oactionPerformed()
són altres nomenclatures habituals. - L'objecte
Event
pot contenir el subjecte, el tipus d'esdeveniment (si hi ha més d'un) i altres paràmetres addicionals d'ajuda per a l'observador. - Quan el subjecte genera diversos tipus d'esdeveniments, podem implementar-ho de diferents maneres:
- Tenir una sola classe
Event
i indicar el seu tipus en un camp amb, per exemple, unenum
. - Implementar
Event
com a una classe abstracta amb subclasses per a cada tipus d'esdeveniment, on cadascuna emmagatzema informació diversa. Això requerirà l'ús deinstanceof
per a distinguir-los. - Tenir diverses signatures del mètode de notificació, una per tipus esdeveniment. Per exemple,
onEventX()
,onEventY()
, etc. L'avantatge és que cada mètode pot tenir paràmetres diferents per cada tipus d'esdeveniment.
- Tenir una sola classe
- Tots aquests mètodes no han de retornar res (tipus void). Només es vol notificar als observadors, però el subjecte no necessita res d'ells.
Callbacks
El patró observador utilitza múltiples noms per a la parella subjecte/observador. El subjecte també pot anomenar-se observable o event source. L'observador també pot anomenar-se handler, listener o callback. Tot depèn del context.
Els callbacks són el mateix concepte explicat diferent. Un callback és un codi que passem com a paràmetre a un component, i que aquest executarà més endavant, possiblement de forma asíncrona. Les callbacks s'utilitzen quan només hi ha un observador per al subjecte.
Per exemple, a JavaFX, un botó té aquest mètode:
void setOnAction(EventHandler<ActionEvent> value)
El botó és el subjecte i EventHandler
és el callback que s'executarà quan es cliqui. Per la seva banda, EventHandler
és una interfície funcional, és a dir, amb un sol mètode:
void handle(ActionEvent event)
El client (codi que utilitza la llibreria) haurà d'implementar el mètode handle
, que rep ActionEvent
, un objecte amb el source, target i type de l'esdeveniment.