Quando dovrei usare la programmazione basata su eventi?


65

Ho passato i callback o ho semplicemente attivato le funzioni di un'altra funzione nei miei programmi per far accadere le cose una volta completate le attività. Quando qualcosa finisce, innesco direttamente la funzione:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

Ma ho letto molte strategie diverse in programmazione, e una che ho capito di essere potente, ma che non ho ancora praticato, è basata sugli eventi (penso che un metodo di cui ho letto si chiamasse "pub-sub" ):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

Mi piacerebbe capire i punti di forza e di debolezza oggettivi della programmazione basata su eventi, piuttosto che chiamare tutte le tue funzioni da altre funzioni. In quali situazioni di programmazione ha senso utilizzare la programmazione basata su eventi?


2
A parte questo, puoi semplicemente usare $(document).bind('snow', shovelShow). Non è necessario avvolgerlo in una funzione anonima.
Karl Bielefeldt,

4
Potresti anche essere interessato a conoscere la "programmazione reattiva", che ha molto in comune con la programmazione guidata dagli eventi.
Eric Lippert,

Risposte:


75

Un evento è una notifica che descrive un'occorrenza del passato recente.

Un'implementazione tipica di un sistema guidato da eventi utilizza un dispatcher di eventi e funzioni di gestore (o abbonati ). Il dispatcher fornisce un'API per collegare i gestori agli eventi (jQuery bind) e un metodo per pubblicare un evento ai suoi abbonati ( triggerin jQuery). Quando si parla di eventi IO o UI, di solito c'è anche un ciclo di eventi , che rileva nuovi eventi come i clic del mouse e li passa al dispatcher. In JS-land, il dispatcher e il loop degli eventi sono forniti dal browser.

Per il codice che interagisce direttamente con l'utente - rispondendo a pressioni di tasti e clic - la programmazione guidata dagli eventi (o una sua variazione, come la programmazione reattiva funzionale ) è quasi inevitabile. Tu, il programmatore, non hai idea di quando o dove l'utente farà clic, quindi spetta al framework GUI o al browser rilevare l'azione dell'utente nel suo ciclo di eventi e notificare il tuo codice. Questo tipo di infrastruttura viene utilizzato anche nelle applicazioni di rete (vedi NodeJS).

Il tuo esempio, in cui si genera un evento nel codice anziché chiamare direttamente una funzione, presenta alcuni compromessi più interessanti, di cui parlerò di seguito. La differenza principale è che l'editore di un evento ( makeItSnow) non specifica il destinatario della chiamata; che è cablato altrove (nella chiamata a bindnel tuo esempio). Questo si chiama fuoco e dimentica : makeItSnowannuncia al mondo che sta nevicando, ma non gli importa chi sta ascoltando, cosa succede dopo o quando succede - semplicemente trasmette il messaggio e si spolvera le mani.


Quindi l'approccio guidato dagli eventi disaccoppia il mittente del messaggio dal destinatario. Un vantaggio che ti offre è che un determinato evento può avere più gestori. È possibile associare una gritRoadsfunzione all'evento neve senza influire sul shovelSnowgestore esistente . Hai flessibilità nel modo in cui l'applicazione è cablata; per disattivare un comportamento è sufficiente rimuovere la bindchiamata anziché cercare il codice per trovare tutte le istanze del comportamento.

Un altro vantaggio della programmazione guidata dagli eventi è che ti dà un posto dove porre preoccupazioni trasversali. Il dispatcher di eventi svolge il ruolo di Mediatore e alcune librerie (come Brighter ) utilizzano una pipeline in modo da poter collegare facilmente requisiti generici come la registrazione o la qualità del servizio.

Informativa completa: Brighter è sviluppato presso Huddle, dove lavoro.

Un terzo vantaggio di disaccoppiare il mittente di un evento dal destinatario è che ti dà flessibilità quando gestisci l'evento. È possibile elaborare ogni tipo di evento sul proprio thread (se il dispatcher di eventi lo supporta) oppure è possibile inserire eventi generati su un broker di messaggi come RabbitMQ e gestirli con un processo asincrono o persino elaborarli in blocco durante la notte. Il destinatario dell'evento potrebbe trovarsi in un processo separato o su una macchina separata. Non è necessario modificare il codice che genera l'evento per farlo! Questa è la grande idea alla base delle architetture "microservice": i servizi autonomi comunicano utilizzando gli eventi, con il middleware di messaggistica come colonna portante dell'applicazione.

Per un esempio piuttosto diverso di stile guidato dagli eventi, guarda alla progettazione guidata dal dominio, in cui gli eventi di dominio vengono utilizzati per aiutare a mantenere separati gli aggregati. Ad esempio, considera un negozio online che consiglia i prodotti in base alla cronologia degli acquisti. A Customerdeve aggiornare la cronologia degli acquisti quando ShoppingCartviene pagata una. L' ShoppingCartaggregato potrebbe avvisare Customersollevando un CheckoutCompletedevento; il Customerotterrebbe aggiornato in una transazione separata in risposta all'evento.


Il principale svantaggio di questo modello guidato dagli eventi è l'indirizzamento indiretto. Ora è più difficile trovare il codice che gestisce l'evento perché non puoi semplicemente navigare ad esso usando il tuo IDE; devi capire dove è legato l'evento nella configurazione e sperare di aver trovato tutti i gestori. Ci sono più cose da tenere in testa in qualsiasi momento. Le convenzioni in stile codice possono aiutare qui (ad esempio, mettendo tutte le chiamate bindin un unico file). Per motivi di sanità mentale, è importante utilizzare solo un dispatcher di eventi e utilizzarlo in modo coerente.

Un altro svantaggio è che è difficile riformattare gli eventi. Se è necessario modificare il formato di un evento, è necessario modificare anche tutti i ricevitori. Questo è esacerbato quando gli abbonati di un evento si trovano su macchine diverse, perché ora è necessario sincronizzare le versioni del software!

In determinate circostanze, le prestazioni possono essere fonte di preoccupazione. Quando elabora un messaggio, il dispatcher deve:

  1. Cerca i gestori corretti in alcune strutture di dati.
  2. Creare una pipeline di elaborazione dei messaggi per ciascun gestore. Ciò può comportare un sacco di allocazioni di memoria.
  3. Chiama dinamicamente i gestori (possibilmente usando la riflessione se la lingua lo richiede).

Questo è certamente più lento di una normale chiamata di funzione, che comporta solo l'inserimento di un nuovo frame nello stack. Tuttavia, la flessibilità offerta da un'architettura guidata dagli eventi rende molto più semplice isolare e ottimizzare il codice lento. Avere la possibilità di inviare lavoro a un processore asincrono è una grande vittoria qui, in quanto ti consente di soddisfare immediatamente una richiesta mentre il duro lavoro viene gestito in background. In ogni caso, se si interagisce con il DB o si disegnano elementi sullo schermo, i costi di IO aumenteranno totalmente i costi di elaborazione di un messaggio. Si tratta di evitare l'ottimizzazione prematura.


In sintesi, gli eventi sono un ottimo modo per creare software liberamente accoppiati, ma non sono privi di costi. Sarebbe un errore, ad esempio, sostituire ogni chiamata di funzione nella tua applicazione con un evento. Usa gli eventi per creare divisioni architettoniche significative.


2
Questa risposta dice la stessa della risposta 5377 che ho selezionato come corretta; Sto cambiando la mia selezione per contrassegnarla perché si elabora ulteriormente.
Viziionary

1
La velocità è uno svantaggio significativo del codice basato sugli eventi? Sembra che potrebbe essere, ma non lo so del tutto.
raptortech97,

1
@ raptortech97 può certamente essere. Per il codice che deve essere particolarmente veloce, probabilmente vorrai evitare di inviare eventi in un ciclo interno; fortunatamente, in tali situazioni di solito è ben definito ciò che devi fare, quindi non hai bisogno di un extra flessibile di eventi (o pubblicazione / iscrizione o osservatori, che sono meccanismi equivalenti con terminologia diversa).
Jules

1
Si noti inoltre che esistono alcune lingue (ad esempio Erlang) costruite attorno al modello dell'attore in cui tutto è messaggi (eventi). In questo caso il compilatore può decidere se implementare i messaggi / eventi come chiamate di funzione dirette o come comunicazione.
Brendan,

1
Per "prestazioni" penso che dobbiamo distinguere tra prestazioni a thread singolo e scalabilità. I messaggi / eventi possono essere peggiori per le prestazioni a thread singolo (ma possono essere convertiti in chiamate di funzione per zero costi aggiuntivi e non essere peggiori) e per la scalabilità è superiore praticamente in tutti i modi (ad esempio, può comportare enormi miglioramenti delle prestazioni sui moderni multi -CPU e futuri sistemi a "molte CPU").
Brendan,

25

La programmazione basata su eventi viene utilizzata quando il programma non controlla la sequenza di eventi che esegue. Invece, il flusso del programma è diretto da un processo esterno come un utente (ad es. GUI), un altro sistema (ad es. Client / server) o un altro processo (ad es. RPC).

Ad esempio, uno script di elaborazione batch sa cosa deve fare, quindi lo fa e basta. E ' non è basata su eventi.

Un word processor si trova lì e attende che l'utente inizi a digitare. I tasti premuti sono eventi che attivano la funzionalità per aggiornare il buffer interno dei documenti. Il programma non può sapere cosa si desidera digitare, quindi deve essere guidato dagli eventi.

La maggior parte dei programmi della GUI è guidata dagli eventi perché costruita attorno all'interazione dell'utente. Tuttavia, i programmi basati su eventi non si limitano alle GUI, che è semplicemente l'esempio più familiare per la maggior parte delle persone. I server Web attendono che i client si connettano e seguano un linguaggio simile. Anche i processi in background sul tuo computer possono rispondere agli eventi. Ad esempio, uno scanner di virus su richiesta può ricevere un evento dal sistema operativo relativo a un file appena creato o aggiornato, quindi scansionare quel file alla ricerca di virus.


18

In un'applicazione basata su eventi il concetto di ascoltatori di eventi ti darà la possibilità di scrivere ancora più applicazioni Loosely Coupled .

Ad esempio un modulo o plug-in di terze parti può eliminare un record dal database e quindi attivare l' receordDeletedevento e lasciare il resto agli ascoltatori di eventi per fare il loro lavoro. Tutto funzionerà bene, anche se il modulo trigger non sa nemmeno chi sta ascoltando questo particolare evento o cosa dovrebbe succedere dopo.


6

Una semplice analogia che volevo aggiungere che mi ha aiutato:

Pensa ai componenti (o agli oggetti) della tua applicazione come a un grande gruppo di amici di Facebook.

Quando uno dei tuoi amici vuole dirti qualcosa, può chiamarti direttamente o pubblicarlo sulla sua bacheca di Facebook. Quando lo pubblicano su Facebook, chiunque può vederlo e reagire ad esso, ma molte persone non lo fanno. A volte è qualcosa di importante che le persone avranno probabilmente bisogno di reagire, come "Stiamo avendo un bambino!" o "La band così-e-così sta facendo un concerto a sorpresa al bar Drunkin 'Clam!". Nell'ultimo caso, probabilmente il resto degli amici dovrà reagire ad esso, specialmente se sono interessati a quella band.

Se il tuo amico vuole mantenere un segreto tra te e loro, probabilmente non lo pubblicheranno sulla loro bacheca di Facebook, ti ​​chiamerebbero direttamente e te lo diranno. Immagina uno scenario in cui dici a una ragazza che ti piace che vorresti incontrarla in un ristorante per un appuntamento. Invece di chiamarla direttamente e chiederle, la pubblichi sulla tua bacheca di Facebook affinché tutti i tuoi amici possano vederla. Funziona, ma se hai un ex geloso, potrebbe vederlo e apparire al ristorante per rovinare la tua giornata.

Quando decidi se costruire o meno ascoltatori di eventi per implementare qualcosa, pensa a questa analogia. Questo componente deve mettere in campo la propria attività affinché chiunque possa vederlo? O devono chiamare qualcuno direttamente? Le cose possono diventare disordinate abbastanza facilmente, quindi fai attenzione.


0

La seguente analogia potrebbe aiutarti a comprendere la programmazione I / O guidata dagli eventi tracciando una linea parallela alla linea d'attesa alla reception del Dottore.

Bloccare l'I / O è come, se sei in coda, l'addetto alla reception chiede a un ragazzo di fronte a te di compilare il modulo e lei aspetta che finisca. Devi aspettare il tuo turno fino a quando il ragazzo termina la sua forma, questo sta bloccando.

Se il ragazzo singolo impiega 3 minuti per compilare, il decimo ragazzo deve attendere fino a 30 minuti. Ora, per ridurre il tempo di attesa di questo decimo ragazzo, la soluzione sarebbe quella di aumentare il numero di receptionist, il che è costoso. Questo è ciò che accade nei server Web tradizionali. Se richiedi le informazioni di un utente, la successiva richiesta di altri utenti dovrebbe attendere il completamento dell'operazione corrente, recuperando dal database. Ciò aumenta il "tempo di risposta" della decima richiesta e aumenta esponenzialmente per l'ennesimo utente. Per evitare questo tradizionale server Web crea thread (equivalente al numero crescente di receptionist) per ogni singola richiesta, ovvero fondamentalmente crea una copia del server per ogni richiesta che è costosi intervalli di consumo della CPU poiché ogni richiesta avrà bisogno di un sistema operativo filo. Per aumentare l'app,

Event Driven : l'altro approccio per aumentare il "tempo di risposta" della coda è quello di adottare un approccio event driven, in cui il tipo in coda verrà consegnato al modulo, a cui verrà chiesto di compilare e tornare al completamento. Quindi l'addetto alla reception può sempre accettare la richiesta. Questo è esattamente ciò che javascript ha fatto sin dal suo inizio. Nel browser, JavaScript rispondeva all'evento clic dell'utente, scorrere, scorrere o recuperare il database e così via. Ciò è possibile in javascript intrinsecamente, poiché javascript considera le funzioni come oggetti di prima classe e possono essere passate come parametri ad altre funzioni (chiamate callback) e possono essere chiamate al completamento di un'attività specifica. Questo è esattamente ciò che fa node.js sul server. Puoi trovare maggiori informazioni sulla programmazione guidata dagli eventi e sugli I / O di blocco, nel contesto del nodo qui

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.