Quand devrais-je utiliser la programmation événementielle?

65

Je passe des rappels ou je déclenche simplement les fonctions d'autres fonctions de mes programmes pour que les choses se passent une fois les tâches terminées. Quand quelque chose finit, je déclenche la fonction directement:

var ground = 'clean';

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

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

Mais j'ai lu beaucoup de stratégies de programmation différentes, et celle que je comprends puissante, mais que je n'ai pas encore pratiquée, est basée sur des événements (je pense qu'une méthode que j'ai lue s'appelait "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);

J'aimerais comprendre les forces et les faiblesses objectives de la programmation événementielle, par opposition à simplement appeler toutes vos fonctions à partir d'autres fonctions. Dans quelles situations de programmation la programmation événementielle a-t-elle un sens?

Viziionary
la source
2
En passant, vous pouvez simplement utiliser $(document).bind('snow', shovelShow). Pas besoin de l'envelopper dans une fonction anonyme.
Karl Bielefeldt
4
Vous voudrez peut-être aussi en savoir plus sur la "programmation réactive", qui a beaucoup de points communs avec la programmation événementielle.
Eric Lippert

Réponses:

75

Un événement est une notification décrivant une occurrence du passé récent.

Une implémentation typique d'un système piloté par événement utilise un répartiteur d'événement et des fonctions de gestionnaire (ou abonnés ). Le répartiteur fournit une API pour relier les gestionnaires aux événements (jQuery bind) et une méthode pour publier un événement à ses abonnés ( triggerdans jQuery). Lorsque vous parlez d'événements IO ou UI, il existe généralement une boucle d'événements , qui détecte les nouveaux événements tels que les clics de souris et les transmet au répartiteur. Dans JS-land, le répartiteur et la boucle d'événement sont fournis par le navigateur.

Pour le code qui interagit directement avec l'utilisateur - répondre aux pressions sur les touches et aux clics - la programmation événementielle (ou une variante de celle-ci, telle que la programmation réactive fonctionnelle ) est presque inévitable. En tant que programmeur, vous ne savez pas quand et où l'utilisateur va cliquer. Vous devez donc utiliser le framework d'interface graphique ou le navigateur pour détecter l'action de l'utilisateur dans sa boucle d'événements et notifier votre code. Ce type d'infrastructure est également utilisé dans les applications réseau (cf. NodeJS).

Votre exemple, dans lequel vous soulevez un événement dans votre code plutôt que d'appeler directement une fonction, présente des compromis plus intéressants, dont je parlerai plus loin. La principale différence est que l'éditeur d'un événement ( makeItSnow) ne spécifie pas le destinataire de l'appel. c'est câblé ailleurs (dans l'appel à bindvotre exemple). C'est ce qu'on appelle feu et oublie : makeItSnowannonce au monde qu'il neige, mais peu importe qui écoute, ce qui se passe ensuite ou quand cela se produit, il diffuse simplement le message et se dépoussière.


Ainsi, l'approche événementielle dissocie l'expéditeur du message du destinataire. Un avantage que cela vous offre est qu'un événement donné peut avoir plusieurs gestionnaires. Vous pouvez lier une gritRoadsfonction à votre événement neige sans affecter le shovelSnowgestionnaire existant . Vous avez la flexibilité dans la manière dont votre application est câblée; pour désactiver un comportement, il vous suffit de supprimer l' bindappel plutôt que de parcourir le code pour en trouver toutes les instances.

Un autre avantage de la programmation événementielle réside dans le fait qu’elle vous donne un endroit où placer vos préoccupations transversales. Le répartiteur d'événements joue le rôle de médiateur et certaines bibliothèques (telles que Brighter ) utilisent un pipeline afin que vous puissiez facilement brancher des exigences génériques telles que la journalisation ou la qualité de service.

Divulgation complète: Brighter est développé à Huddle, où je travaille.

Un troisième avantage de découplage de l'expéditeur d'un événement du récepteur est qu'il vous donne la flexibilité lorsque vous gérez l'événement. Vous pouvez traiter chaque type d'événement sur son propre thread (si votre répartiteur d'événements le prend en charge) ou vous pouvez placer les événements déclenchés sur un courtier de messages tel que RabbitMQ et les gérer avec un processus asynchrone, voire les traiter en bloc du jour au lendemain. Le destinataire de l'événement peut se trouver dans un processus séparé ou sur une machine séparée. Vous n'avez pas à changer le code qui déclenche l'événement pour le faire! C'est la grande idée derrière les architectures de "microservices": des services autonomes communiquent à l'aide d'événements, avec un middleware de messagerie comme épine dorsale de l'application.

Pour un exemple assez différent de style piloté par les événements, reportez-vous à la conception pilotée par domaine , où les événements de domaine sont utilisés pour aider à garder les agrégats séparés. Par exemple, considérons un magasin en ligne qui recommande des produits en fonction de votre historique d’achat. A Customerdoit avoir son historique d’achats mis à jour lorsqu’un ShoppingCartpaiement est effectué. L' ShoppingCartagrégat peut notifier le Customeren soulevant un CheckoutCompletedévénement; le Customerserait mis à jour dans une transaction séparée en réponse à l'événement.


Le principal inconvénient de ce modèle événementiel est l’indirection. Il est maintenant plus difficile de trouver le code qui gère l'événement car vous ne pouvez pas y accéder simplement à l'aide de votre IDE; vous devez déterminer où l'événement est lié dans la configuration et espérer que vous avez trouvé tous les gestionnaires. Il y a plus de choses à garder dans la tête à tout moment. Les conventions de style de code peuvent être utiles ici (par exemple, placer tous les appels binddans un fichier). Pour des raisons de santé mentale, il est important de n'utiliser qu'un seul répartiteur d'événements et de l'utiliser de manière cohérente.

Un autre inconvénient est qu’il est difficile de modifier les événements. Si vous devez modifier le format d'un événement, vous devez également modifier tous les destinataires. Cela est exacerbé lorsque les abonnés d'un événement se trouvent sur différentes machines, car vous devez maintenant synchroniser les versions de logiciels!

Dans certaines circonstances, la performance peut être une préoccupation. Lors du traitement d'un message, le répartiteur doit:

  1. Recherchez les gestionnaires appropriés dans certaines structures de données.
  2. Construisez un pipeline de traitement de messages pour chaque gestionnaire. Cela peut impliquer un tas d’allocations de mémoire.
  3. Appelez dynamiquement les gestionnaires (en utilisant éventuellement la réflexion si la langue le requiert).

Ceci est certainement plus lent qu'un appel de fonction normal, qui consiste uniquement à pousser un nouveau cadre sur la pile. Cependant, la flexibilité offerte par une architecture pilotée par les événements facilite beaucoup l'isolement et l'optimisation du code lent. Avoir la possibilité de soumettre du travail à un processeur asynchrone est une grande victoire ici, car cela vous permet de servir une demande immédiatement alors que le travail acharné est traité en arrière-plan. Quoi qu’il en soit, si vous interagissez avec la base de données ou si vous dessinez à l’écran, les coûts liés aux E / S vont totalement couvrir les coûts de traitement d’un message. Il s'agit d'éviter une optimisation prématurée.


En résumé, les événements sont un excellent moyen de créer des logiciels à couplage lâche, mais ils ne sont pas sans coût. Ce serait une erreur, par exemple, de remplacer chaque appel de fonction dans votre application par un événement. Utilisez les événements pour créer des divisions architecturales significatives.

Benjamin Hodgson
la source
2
Cette réponse dit la même chose que la réponse de 5377 que j'ai sélectionnée comme correcte; Je modifie ma sélection pour marquer celle-ci car elle est plus détaillée.
Viziionary
1
La vitesse est-elle un inconvénient majeur du code événementiel? Cela semble être le cas, mais je ne sais pas trop.
raptortech97
1
@ raptortech97 ça peut certainement l'être. Pour le code qui doit être particulièrement rapide, vous voudrez probablement éviter d'envoyer des événements dans une boucle interne; heureusement, dans de telles situations, ce que vous devez faire est généralement bien défini, vous n'avez donc pas besoin de la souplesse supplémentaire des événements (ou publication / abonnement ou observateurs, qui sont des mécanismes équivalents avec une terminologie différente).
Jules
1
Notez également qu'il existe certaines langues (par exemple, Erlang) construites autour du modèle d'acteur où tout est constitué de messages (événements). Dans ce cas, le compilateur peut décider d'implémenter les messages / événements en tant qu'appels de fonction directs ou en tant que communication.
Brendan
1
Pour "performance", je pense que nous devons faire la distinction entre performance mono-thread et évolutivité. Les messages / événements peuvent être plus dommageables pour les performances mono-threadées (mais peuvent être convertis en appels de fonctions pour un coût supplémentaire nul et non préjudiciable), et pour l'évolutivité, ils sont supérieurs à presque tous les égards (par exemple, ils entraîneront des améliorations massives des performances sur les serveurs modernes modernes). -CPU et futurs systèmes "multi-processeurs").
Brendan
25

La programmation basée sur les événements est utilisée lorsque le programme ne contrôle pas la séquence d'événements qu'il exécute. Au lieu de cela, le flux de programme est dirigé par un processus externe tel qu'un utilisateur (par exemple une interface graphique), un autre système (par exemple un client / serveur) ou un autre processus (par exemple un RPC).

Par exemple, un script de traitement par lots sait ce qu'il doit faire, il le fait simplement. Ce n'est pas événementiel.

Un traitement de texte est assis à cet endroit et attend que l'utilisateur commence à taper. Les touches sont des événements qui déclenchent une fonctionnalité permettant de mettre à jour le tampon de document interne. Le programme ne peut pas savoir ce que vous voulez taper, il doit donc être événementiel.

La plupart des programmes d'interface graphique sont pilotés par les événements car ils sont construits autour de l'interaction utilisateur. Cependant, les programmes événementiels ne se limitent pas aux interfaces graphiques, c'est simplement l'exemple le plus familier pour la plupart des gens. Les serveurs Web attendent que les clients se connectent et suivent un idiome similaire. Les processus en arrière-plan sur votre ordinateur peuvent également réagir à des événements. Par exemple, un analyseur de virus à la demande peut recevoir un événement du système d'exploitation concernant un fichier nouvellement créé ou mis à jour, puis analyser ce fichier à la recherche de virus.


la source
18

Dans une application basée sur les événements, le concept d’ écouteurs d’événements vous donnera la possibilité d’écrire encore plus d’ applications lâchement couplées .

Par exemple, un module ou un plug-in tiers peut supprimer un enregistrement de la base de données, puis déclencher l' receordDeletedévénement et laisser le reste aux écouteurs d'événement faire leur travail. Tout fonctionnera bien, même si le module déclencheur ne sait même pas qui est à l'écoute de cet événement en particulier ni de ce qui devrait se passer par la suite.

53777A
la source
6

Une simple analogie que je voulais ajouter qui m'a aidé:

Pensez aux composants (ou objets) de votre application comme à un grand groupe d'amis Facebook.

Lorsqu'un de vos amis veut vous dire quelque chose, il peut vous appeler directement ou le poster sur son mur Facebook. Quand ils le publient sur leur Facebook, tout le monde peut le voir et y réagir, mais beaucoup ne le font pas. Parfois, il est important que les gens réagissent, comme "Nous allons avoir un bébé!" ou "Un tel groupe fait un concert surprise au bar Drunkin 'Clam!". Dans ce dernier cas, le reste des amis devront probablement réagir, en particulier s’ils sont intéressés par ce groupe.

Si votre ami veut garder un secret entre vous et eux, ils ne le publieraient probablement pas sur leur mur Facebook, ils vous contacteraient directement pour vous le dire. Imaginez un scénario dans lequel vous dites à une fille que vous aimez que vous souhaitez la rencontrer dans un restaurant pour un rendez-vous. Au lieu de l'appeler directement et de lui demander, affichez-la sur votre mur Facebook pour que tous vos amis puissent la voir. Cela fonctionne, mais si vous avez un ex jaloux, alors elle pourrait le voir et apparaître au restaurant pour gâcher votre journée.

Lorsque vous décidez d'intégrer ou non des écouteurs d'événements pour implémenter quelque chose, pensez à cette analogie. Ce composant doit-il mettre son activité à la portée de tous? Ou ont-ils besoin d'appeler quelqu'un directement? Les choses peuvent se gâter assez facilement, alors faites attention.

Arjabbar
la source
0

Cette analogie suivante pourrait vous aider à comprendre la programmation d'E / S pilotée par les événements en traçant un parallèle avec la file d'attente à la réception du médecin.

Le blocage des E / S revient à dire que si vous êtes dans la file d'attente, la réceptionniste demande à un gars en face de vous de remplir le formulaire et elle attend jusqu'à ce qu'il ait terminé. Vous devez attendre votre tour jusqu'à ce que le gars termine sa forme, cela bloque.

Si un célibataire met trois minutes à remplir, le dixième doit attendre 30 minutes. Maintenant, pour réduire ce temps d’attente du 10ème gars, la solution serait d’augmenter le nombre de réceptionnistes, ce qui est coûteux. C'est ce qui se passe dans les serveurs Web traditionnels. Si vous demandez des informations sur l'utilisateur, les autres utilisateurs devront attendre que l'opération en cours, à partir de la base de données, soit terminée. Cela augmente le "délai de réponse" de la dixième demande et augmente exponentiellement pour le nième utilisateur. Pour éviter cela, les serveurs Web traditionnels créent un thread (équivalent à un nombre croissant de réceptionnistes) pour chaque requête, c’est-à-dire qu’il crée fondamentalement une copie du serveur pour chaque requête, ce qui représente un coût élevé en consommation de processeur, car chaque requête nécessite un système d’exploitation. fil. Pour mettre à l'échelle l'application,

Event Driven : L'autre approche pour augmenter le "temps de réponse" de la file d'attente consiste à opter pour une approche événementielle, où le gars dans la file d'attente sera remis au formulaire, il lui sera demandé de remplir et de revenir à la fin. Par conséquent, la réceptionniste peut toujours prendre une demande. C’est exactement ce que fait javascript depuis sa création. Dans le navigateur, javascript répond aux événements de clic, défilement, balayage ou extraction de la base de données, etc. Cela est possible en javascript de manière inhérente, car javascript traite les fonctions en tant qu'objets de première classe et peut être transmis en tant que paramètre à d'autres fonctions (appelées rappels) et peut être appelé à la fin d'une tâche particulière. C'est exactement ce que fait node.js sur le serveur. Vous trouverez plus d'informations sur la programmation par événement et le blocage d'E / S dans le contexte du nœud ici

vijay kumar
la source