J'ai passé des rappels ou simplement déclenché les fonctions d'une autre fonction dans mes programmes pour faire bouger les choses une fois les tâches terminées. Lorsque quelque chose se termine, je déclenche directement la fonction:
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 de nombreuses stratégies différentes en programmation, et celle que je comprends être puissante, mais que je n'ai pas encore pratiquée, est basée sur les é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 basée sur les événements, par rapport à simplement appeler toutes vos fonctions à partir d'autres fonctions. Dans quelles situations de programmation la programmation basée sur les événements a-t-elle un sens à utiliser?
Un événement est une notification décrivant une occurrence du passé récent.
Une implémentation typique d'un système événementiel utilise un répartiteur d'événements et des fonctions de gestionnaire (ou abonnés ). Le répartiteur fournit une API pour câbler les gestionnaires jusqu'aux événements (jQuery's bind
) et une méthode pour publier un événement à ses abonnés (trigger
dans jQuery). Lorsque vous parlez de IO ou événements d'interface utilisateur, il y a aussi généralement une boucle d'événement , qui détecte de nouveaux événements tels que en cliquant avec la souris et les transmet au répartiteur. Dans JS-land, le répartiteur et la boucle d'événements sont fournis par le navigateur.
Pour le code qui interagit directement avec l'utilisateur - répondant aux pressions et aux clics - la programmation événementielle (ou une variante de celle-ci, comme programmation réactive fonctionnelle ) est presque inévitable. Vous, le programmeur, n'avez aucune idée du moment ou de l'endroit où l'utilisateur va cliquer, c'est donc au framework GUI ou au navigateur de détecter l'action de l'utilisateur dans sa boucle d'événement et de notifier votre code. Ce type d'infrastructure est également utilisé dans les applications de mise en réseau (cf. NodeJS).
Votre exemple, dans lequel vous déclenchez un événement dans votre code plutôt que d'appeler directement une fonction, a des compromis plus intéressants, dont je parlerai ci-dessous. La principale différence est que l'éditeur d'un événement (makeItSnow
) ne spécifie pas le destinataire de l'appel; qui est câblé ailleurs (dans l'appel à bind
dans votre exemple). C'est ce qu'on appelle feu-et-oublier : makeItSnow
annonce 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.
L'approche événementielle dissocie donc l'expéditeur du message du récepteur. Un avantage que cela vous offre est qu'un événement donné peut avoir plusieurs gestionnaires. Vous pouvez lier une fonction gritRoads
à votre événement snow sans affecter le gestionnaire shovelSnow
existant. Vous avez la flexibilité dans la façon dont votre application est câblée; pour désactiver un comportement, il vous suffit de supprimer l'appel bind
plutôt que de parcourir le code pour rechercher toutes les instances du comportement.
Un autre avantage de la programmation événementielle est qu'elle vous donne un endroit où mettre des préoccupations transversales. Le répartiteur d'événements joue le rôle de Mediator , 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é chez Huddle, où je travaille.
Un troisième avantage du découplage de l'expéditeur d'un événement du récepteur est qu'il vous donne de la flexibilité dans 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 des événements déclenchés sur un courtier de messages tels que RabbitMQ et les gérer avec un processus asynchrone ou même un processus les en vrac pendant la nuit. Le destinataire de l'événement pourrait être dans un processus distinct ou sur une machine distincte. Vous n'avez pas à changer le code qui déclenche l'événement pour ce faire! C'est la grande idée derrière les architectures de "microservices": les services autonomes communiquent à l'aide d'événements, avec le middleware de messagerie comme colonne vertébrale de l'application.
Pour un exemple assez différent de style piloté par les événements, regardez la conception pilotée par domaine, où événements de domaine sont utilisés pour aider garder les agrégats séparés. Par exemple, envisagez une boutique en ligne qui recommande des produits en fonction de votre historique d'achat. Un Customer
doit avoir son historique d'achat mis à jour lorsqu'un ShoppingCart
est payé. L'agrégat ShoppingCart
peut notifier Customer
en déclenchant un événement CheckoutCompleted
; le Customer
serait mis à jour dans une transaction distincte en réponse à l'événement.
Le principal inconvénient de ce modèle événementiel est l'indirection. Il est désormais plus difficile de trouver le code qui gère l'événement, car vous ne pouvez pas simplement y accéder à l'aide de votre IDE; vous devez déterminer où l'événement est lié dans la configuration et espérer avoir trouvé tous les gestionnaires. Il y a plus de choses à garder en tête à tout moment. Les conventions de style de code peuvent aider ici (par exemple, mettre tous les appels à bind
dans un fichier). Pour votre 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 refactoriser les événements. Si vous devez changer le format d'un événement, vous devez également changer tous les récepteurs. Ceci est exacerbé lorsque les abonnés d'un événement se trouvent sur différentes machines, car vous devez maintenant synchroniser les versions logicielles!
Dans certaines circonstances, la performance peut être un problème. Lors du traitement d'un message, le répartiteur doit:
C'est certainement plus lent qu'un appel de fonction normal, ce qui implique seulement de pousser un nouveau cadre sur la pile. Cependant, la flexibilité que vous offre une architecture événementielle 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 tandis que le travail acharné est traité en arrière-plan. Dans tous les cas, si vous interagissez avec la base de données ou dessinez des trucs à l'écran, les coûts de IO vont totalement submerger 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 gratuits. Ce serait une erreur, par exemple, de remplacer chaque appel de fonction dans votre application par un événement. Utilisez des événements pour créer des divisions architecturales significatives.
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 effectue. Au lieu de cela, le flux de programme est dirigé par un processus externe tel qu'un utilisateur (par exemple GUI), un autre système (par exemple client/serveur) ou un autre processus (par exemple RPC).
Par exemple, un script de traitement par lots sait ce qu'il doit faire, il le fait donc simplement. C'est pas basé sur les événements.
Un traitement de texte se trouve là et attend que l'utilisateur commence à taper. Les touches sont des événements qui déclenchent une fonctionnalité de mise à jour du tampon de document interne. Le programme ne peut pas savoir ce que vous voulez taper, il doit donc être piloté par les événements.
La plupart des programmes GUI sont pilotés par les événements car ils sont construits autour de l'interaction utilisateur. Cependant, les programmes basés sur des événements ne se limitent pas aux interfaces graphiques, c'est simplement l'exemple le plus familier à la plupart des gens. Les serveurs Web attendent que les clients se connectent et suivent un idiome similaire. Les processus d'arrière-plan sur votre ordinateur peuvent également répondre aux événements. Par exemple, un antivirus à 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.
Dans une application basée sur des événements, le concept de écouteurs d'événements vous donnera la possibilité d'écrire encore plus Couplement lâche applications.
Par exemple, un module ou un plug-in tiers peut supprimer un enregistrement de la base de données, puis déclencher l'événement receordDeleted
et laisser le reste aux écouteurs d'événements pour faire leur travail. Tout fonctionnera bien, même si le module de déclenchement ne sait même pas qui écoute cet événement particulier ou ce qui devrait se passer ensuite.
Une analogie simple que je voulais ajouter qui m'a aidé:
Considérez les composants (ou objets) de votre application comme un grand groupe d'amis Facebook.
Lorsque l'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, alors n'importe qui pourrait le voir et y réagir, mais beaucoup de gens ne le font pas ' t. Parfois, c'est quelque chose d'important dont les gens auront probablement besoin pour réagir, comme "Nous allons avoir un bébé!" ou "Le groupe Untel fait un concert surprise au bar Drunkin 'Clam!". Dans le dernier cas, alors les autres amis devront probablement y réagir, surtout s'ils sont intéressés par ce groupe.
Si votre ami veut garder un secret entre vous et lui, il ne le posterait probablement pas sur son mur Facebook, il vous appellerait directement et vous le dirait. Imaginez un scénario où vous dites à une fille que vous aimez que vous aimeriez la rencontrer dans un restaurant pour un rendez-vous. Au lieu de l'appeler directement et de lui demander, vous la postez sur votre mur Facebook pour que tous vos amis puissent la voir. Cela fonctionne, mais si vous avez un ex jaloux, elle pourrait voir cela et apparaître au restaurant pour gâcher votre journée.
Lorsque vous décidez de créer ou non des écouteurs d'événements pour implémenter quelque chose, pensez à cette analogie. Cette composante doit-elle mettre son entreprise en avant pour que quiconque la voie? Ou ont-ils besoin d'appeler quelqu'un directement? Les choses peuvent devenir désordonnées assez facilement, alors faites attention.
Cette analogie suivante peut vous aider à comprendre la programmation d'E/S pilotée par les événements en établissant un parallèle avec la file d'attente à la réception du médecin.
Bloquer les E/S, c'est comme si vous êtes dans la file d'attente, la réceptionniste demande à un gars devant vous de remplir le formulaire et elle attend qu'il ait fini. Vous devez attendre votre tour jusqu'à ce que le gars termine sa forme, c'est bloquant.
Si un gars célibataire met 3 minutes à remplir, le 10e gars doit attendre 30 minutes. Maintenant, pour réduire ce 10e temps d'attente, 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 un utilisateur, la demande ultérieure par d'autres utilisateurs doit attendre la fin de l'opération en cours, la récupération à partir de la base de données. Cela augmente le "temps de réponse" de la 10e demande et augmente de façon exponentielle 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 demande, c'est-à-dire, fondamentalement, il crée une copie du serveur pour chaque demande, ce qui est coûteux en termes de consommation de CPU car chaque demande aura besoin d'un système d'exploitation fil. Pour faire évoluer l'application, vous devez injecter beaucoup de puissance de calcul dans l'application.
Event Driven : L'autre approche pour augmenter le "temps de réponse" de la file d'attente est d'opter pour une approche événementielle, où les gars dans la file d'attente seront remis formulaire, demandé de remplir et de revenir à la fin. Par conséquent, la réceptionniste peut toujours prendre la demande. C'est exactement ce que javascript fait depuis sa création. Dans le navigateur, javascript répondrait à l'événement de clic de l'utilisateur, au défilement, au balayage ou à la récupération de la base de données, etc. Cela est possible en javascript de manière inhérente, car javascript traite les fonctions comme des objets de première classe et ils peuvent être passés en tant que paramètres à d'autres fonctions (appelés rappels) et peuvent être appelés à la fin d'une tâche particulière. C'est exactement ce que fait node.js sur le serveur. Vous pouvez trouver plus d'informations sur la programmation événementielle et le blocage des E/S, dans le contexte du nœud ici