C# - Programmation événementielle
Après cette quête tu seras en mesure d'utiliser le paradigme événementiel pour concevoir une solution logicielle
Un paradigme de programmation
La programmation événementielle est un paradigme différent du reste. Il propose une architecture et une manière de résoudre les problèmes posés par une application plus intuitive pour un utilisateur que d'autres paradigmes tout en permettant une grande flexibilité. En effet, ce paradigme a de différent que l'application est pilotée par les choix de l'utilisateur, et non pas par le flux d'exécution procédural que l'on rencontre habituellement.
Le principe de la programmation dirigée par les événements est de contrôler le flux d'exécution du programme en utilisant ... des événements ! La logique de ce paradigme permet à une entité logicielle réalisant une action de notifier d'autres entités logicielles de réagir à l'événement lancé d'une certaine manière.
Prenons l'exemple d'une facture de produits sur un site E-commerce:
-
Un utilisateur du site commande 3 mêmes produits à 5 €
-
Il veut procéder au paiement et passe d'abord par le récapitulatif du panier
-
Le récapitulatif du panier indique à l'utilisateur qu'il s'apprête à acheter trois produits et que le total donne 15 €
-
L'utilisateur se rend compte qu'il en veut un quatrième et donc en ajoute un directement sur le récapitulatif du panier
-
Le prix sur le récapitulatif panier se met automatiquement à jour
Nous avons dans la situation précédente un bon exemple d'utilisation du paradigme événementiel: lorsque l'utilisateur réalise l'action d'ajouter un produit dans son récapitulatif panier, le prix se met automatiquement à jour. Ainsi, une action précise de l'utilisateur implique la modification d'autres entités logicielles.

Ainsi, le paradigme événementiel est très simple à comprendre pour les profanes: c'est une simple relation de cause à effet. Ce paradigme a beau être intuitif, il n'en est pas moins difficile de l'implémenter et de concevoir une architecture compréhensible et solide. Les programmes pilotés par les événements peuvent être difficiles à comprendre et peu performant pour des petits programmes. Il est cependant très adapté à de grosses applications et surtout aux interfaces graphiques, aux jeux, aux sites web ...
Mais au fait, comment ça marche le paradigme événementiel ?
Article sur le programmation pilotée par les événements
L'observateur: un patron de conception
Il est usage dans le monde du développement d'avoir déjà des solutions aux problèmes récurrents rencontrés durant l'écriture d'un logiciel. Et la programmation événementielle ne fait pas défaut: il existe des techniques et des patrons de conception nous permettant de mettre en place une architecture événementielle.
Il existe plusieurs patrons de conceptions qui permettent d'implémenter une architecture événementielle. Le plus simple, intuitif et direct pour implémenter ce type d'architecture est le design pattern de l'Observateur.

Le design pattern de l'Observateur se compose de deux participants:
- Un Publisher ou Subject - Il notifie une collection d'Observer des derniers événements
- Un Listener ou Observer - Il observe les notifications du Publisher
L'implémentation est relativement simple: chaque objet de type Observer a une méthode update qui est appelée lorsqu'un événement est reçu. Les objets de type Observer doivent souscrire à un Publisher/Subject qui notifie les Observer qu'il y a eu un événement.
Ainsi, lors d'un événement, les Observer réalisent le travail qu'ils ont à faire en fonction d'un événement en particulier. A chaque événement lancé les Observer vont réagir à des événements particuliers. Il est possible de contrôler finement dans le publisher quel Observer sera notifié et l'Observer peut décider de réagir différemment à des événements différents.: il peut par exemple lancer un nouvel événement qui sera notifié à tous les autres Observer.
Présentation de l'Observateur
Gérer les événements en C#
Nous n'avons pas besoin d'écrire le design pattern de l'Observateur pour coder une application pilotée par les événements en C#. En effet, un système de gestion d'événements est présent en C# et nous facilite énormément le travail de réaliser un programme piloté par les événements.
Le système de gestion des événements de C# est très proche du patron de conception de l'Observateur. Quasi-pareil car il ne s'agit pas du même design pattern que l'_Observateur _mais l'un de ses enfants: le Publisher-Subscriber. Les deux design patterns diffèrent (malgré le fait que peu de développeurs le reconnaissent) d'un point unique mais non-négligeable: il y a un médiateur entre les Observer/Subscriber et le Subject/Publisher. En effet, plutôt que de passer directement les événements à un objet en particulier, il y a une file d'événements qui est lue par les Observer/Subscriber. Ainsi, un couplage faible est réalisé entre Observer/Subscriber et Subject/Publisher.
Le système d'événements de C# se rapproche plus du pattern Publisher-Subscriber sans vraiment l'être à 100% car il implémente une queue d'événements mais sans médiateur _concret _entre Subject/Publisher et Observer/Subscriber à proprement parler.
Du code valant souvent mieux que des mots, place à un exemple pratique:
using System;
namespace EventTest
{
/// <summary>
/// Main program
/// </summary>
/// <remarks>
/// This class is the <see cref="LogEvent"/> event publisher
/// </remarks>
class Program
{
public event EventHandler<SendLogEventArgs> OnSendLog;
static void Main(string[] args)
{
var program = new Program();
var logger = new StandardOutputLogger();
// Subscribe the logger to OnSendLog event
logger.Subscribe(program);
var eventArgs = new SendLogEventArgs("LogEvent published", DateTime.Now);
// Dispatch OnSendLog subscribed loggers
if (program.OnSendLog != null)
{
program.OnSendLog(program, eventArgs);
}
}
}
/// <summary>
/// Logs messages to the standard output
/// </summary>
/// <remarks>
/// This class is a subscriber for <see cref="LogEvent"/>
/// </remarks>
class StandardOutputLogger
{
/// <summary>
/// Subscribe to the log publisher
/// </summary>
public void Subscribe(Program program)
{
program.OnSendLog += OnLogSent;
}
/// <summary>
/// Delegate called when a log is sent by the publisher
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnLogSent(object sender, SendLogEventArgs args)
{
// When OnLogSent is called, it writes a log on the stream
Write(args.Message, args.DateTime);
}
/// <summary>
/// Write a log to the standard output
/// </summary>
/// <param name="message"></param>
/// <param name="dateTime"></param>
public void Write(String message, DateTime? dateTime = null)
{
if (dateTime == null)
{
dateTime = DateTime.Now;
}
String formattedMessage = String.Format("{0} - {1}", dateTime, message);
Console.WriteLine(formattedMessage);
}
}
/// <summary>
/// Arguments of a log
/// </summary>
/// <remarks>
/// This class carries out a log message and the date when the message was sent
/// </remarks>
class SendLogEventArgs : EventArgs
{
public String Message;
public DateTime DateTime;
public SendLogEventArgs(String message, DateTime dateTime)
{
Message = message;
DateTime = dateTime;
}
}
}
Le code ci-dessus permet d'envoyer des logs en utilisant un événement. Le principe d'envoi/réception est un petit peu compliqué à comprendre au départ mais devient peu à peu fluide dans le cerveau:
-
On crée une classe représentant les arguments d'un ou plusieurs évéments (
SendLogEventArgs) -
On crée un événement dans une classe qui le publie (Program). Pour ce faire on ajoute un attribut public de type
EventHandler.EventHandlerest un type générique qui spécialise un sous-type deEventArgs, on peut lui ajouter de multiple delegates qui seront exécutées lorsque l'événement sera invoqué. -
On crée une ou plusieurs classes qui écoutent un événement. On écrit une méthode exécutée lorsqu'un événement est reçu (
StandardOutputLogger.OnLogSent) -
On relie dans le Publisher/Subject des delegates en ajoutant dans l'
EventHandlerdu a méthode de l'Observer/Subscriber exécutée lors de la réception d'un événement. -
On envoie l'événement en l'invoquant si il n'est pas vide.
Article sur la fine distinction entre Observateur et Publisher-Subscriber
Vidéo démontrant le mécanisme événementiel en C#
Challenge
Plusieurs loggers
Ce challenge consiste à notifier plusieurs loggers en utilisant le paradigme événementiel. Un log est envoyé à plusieurs loggers à l'aide d'un événement dispatché par un publisher
Critères de validation
-
La solution est hébergée sur GitHub
-
Une classe
StandardOutputLoggerécrit des logs sur la sortie standard -
Une classe
FileStreamOutputLoggerécrit des logs dans un fichier -
Un log est écrit par l'envoi d'un événement par un publisher
-
A chaque envoi de log, une classe
StandardOutputLoggeret une autreFileStreamOutputLoggersont notifiées d'un événementOnLogSendenvoyé par un publisher. -
Une méthode
HandleLogSends'occupe de gérer la réception de l'événement côté logger. Il écrit sur le flux associé à son instance.