Programmation réactive
En informatique, la programmation réactive est un paradigme de programmation visant à conserver une cohérence d'ensemble en propageant les modifications d'une source réactive (modification d'une variable, entrée utilisateur, etc.) aux éléments dépendants de cette source.
Historique
La programmation réactive est née à partir du patron de conception observateur afin de corriger ses défauts[1], parmi lesquels :
- Encapsulation
- Le patron de conception Observateur est souvent utilisé pour faire des machines à états. Malheureusement, les états sont souvent sauvegardés dans des variables qui doivent être visibles par tous les observateurs[2].
- Manque de composabilité
- Le code qui en résulte n'est pas assez composable. Par exemple, il n'est pas toujours facile d'ajouter ou de supprimer un glisser-déposer (drag and drop) à un élément lorsqu'il y a beaucoup d'observateurs qui ont plus ou moins la même fonctionnalité[2].
- Lisibilité réduite
- Il est difficile de faire correspondre les entrées aux sorties[3] (syndrome du plat de spaghettis).
- Gestion des ressources
- La vie d'un observateur doit être spécifiée explicitement, ce qui peut provoquer une mauvaise gestion des ressources. Dans certains cas, pour des soucis de performances, il est utile de n'activer un observateur qu'au moment où nous en avons besoin. Exemple : si l'utilisateur n'utilise pas le glisser-déposer (drag and drop), il est alors inutile de récupérer les coordonnées de la souris sans cesse[4].
- Séparation des préoccupations
- Il faut diviser le code de sorte que chaque méthode ait sa propre fonctionnalité, et que celle-ci soit unique, autrement dit, il faut respecter le SRP (Single Responsibility Principle). Exemple : Dans le cas d'une application qui sert à dessiner avec la souris, les observateurs de l'application ne vont pas que construire le chemin de la souris mais vont aussi appeler des méthodes pour l'afficher, ce qui n'est pas une bonne pratique[5].
- Cohérence des données
- Avec le patron de conception Observateur, il n'y a aucune garantie que les données restent cohérentes[5].
- Uniformité
- Le fait que la mise en place des différents observateurs est effectuée par plusieurs méthodes, diminue l'uniformité du code[4].
Importance de la programmation réactive
La plupart des applications contemporaines sont réactives, c'est-à-dire qu'elles répondent, en calculant, à des événements qui leur parviennent[1].
Cela permet aux utilisateurs d'avoir une meilleure interaction avec le système, une réponse beaucoup plus rapidement et donc une satisfaction[3].
La réactivité est désormais présente partout, et principalement dans les interfaces graphiques[1].
Les applications réactives sont importantes aujourd'hui car elles doivent être :
- Disponibles
- le système doit répondre rapidement quoi qu'il arrive.
- Résilientes
- le système doit toujours rester disponible, même en cas d'erreur.
- Souples
- le système doit continuer à vivre même s'il est surchargé.
- Orientées messages
- le système utilise des messages asynchrones.
Ce qui correspond aux exigences des utilisateurs d'aujourd'hui[6].
Fonctionnement
Description générale
Chaque variable est avant tout initialisée lors de l'analyse des expressions et les dépendances entre les variables sont enregistrées, de sorte que, dès qu'une variable subit une mise à jour (événement), alors toutes les variables qui en dépendent, subissent elles aussi une mise à jour de leur valeur. Par la suite, elles peuvent, elles aussi, déclencher la mise à jour d'autres variables et ainsi de suite[1].
La programmation réactive fait alors en sorte que chaque expression soit correcte à tout instant[1].
Par exemple :
a := 50 b := a + 22 c := a + 6 d := b + c
Après avoir analysé les expressions, voici les résultats :
a | b | c | d |
---|---|---|---|
50 | 72 | 56 | 128 |
Et voici les dépendances :
a | b | c | d |
---|---|---|---|
a | a | b , c |
Supposons désormais que la valeur de a change et passe à 10.
En programmation réactive, puisque b et c dépendent de a, alors b et c vont être automatiquement mises à jour.
a | b | c | d |
---|---|---|---|
10 | 32 | 16 | 128 |
De la même manière, b et c viennent d'être mis à jour. Par conséquent, puisque d dépend de b et c, alors d subira lui aussi une mise à jour.
a | b | c | d |
---|---|---|---|
10 | 32 | 16 | 48 |
Il suffit d'une seule modification de valeur pour que la variable se mette à jour.
Concepts
La programmation réactive est basée sur des concepts comme des variations des valeurs dans le temps, des flots d’événements pour modéliser les mises à jour discrètes, des suivis de dépendances, et des propagations automatiques du changement[1].
Valeurs qui varient dans le temps
À tout instant t de l'exécution du programme, toutes les expressions du programme doivent rester correctes. Par conséquent, les valeurs des variables évoluent dans le temps au fur et à mesure de l'exécution du programme de manière automatique et discrète[1].
Flots d'événements
Lorsqu'une variable change de valeur, toutes les variables qui dépendent d'elle sont mises à jour, ce qui nécessite un événement / message.
Il est important en programmation réactive d'écouter tous les événements qui peuvent survenir.
Suivis de dépendances
Les dépendances entre les variables, directes ou indirectes, doivent être suivies. Si une nouvelle dépendance fait son apparition, alors elle devra être sauvegardée afin de pouvoir propager dans un futur proche, d'éventuelles mises à jour de valeurs[1].
Propagations automatiques du changement
Lorsqu'une variable subit une mise à jour, toutes les autres variables qui en dépendent doivent être informées afin qu'elles puissent se mettre à jour.
La propagation doit se poursuivre automatiquement jusqu'à ce que toutes les variables qui en dépendent directement ou indirectement soient à jour[1].
Langages de programmation réactifs
Les langages de programmation réactive entrent dans la catégorie des langages orientés flots de contrôle[7].
- RxJava[8]
- RxJS[8]
- Rx.Net[8]
- RxScala[8]
- RxClojure[8]
- RxSwift[8]
Apports et limitations
Apports
- Il n'est plus possible d'avoir des bugs à cause d'un oubli de mise à jour d'une valeur[1].
- Gain de temps à l'exécution[3].
- Certaines applications sont plus simples à écrire en programmation réactive[9].
- Le programmeur n'a plus à se soucier de l'ordre de déclenchement des événements[10].
Limitations
- La programmation réactive est difficile à implémenter[3].
- La programmation réactive est asynchrone, donc, il est difficile de comprendre et de suivre les flux[1].
- Difficile de coder un compilateur.
Approches
Observateur (patron de conception)
L’approche traditionnelle pour implémenter des applications réactives est le patron de conception observateur qui sépare les producteurs d’événements (observables) des consommateurs d’événements (observateurs)[1].
Programmation orientée objet
Il s'agit ici, à la fois d'utiliser de la programmation réactive et de la programmation orientée objet.
Méthodes et attributs
L'approche qui semble la plus naturelle est de remplacer pour chaque objet les méthodes et les attributs par des réactions. Ainsi, les attributs se mettront tout seuls à jour.
Par exemple, il est possible de faire un mutateur qui ne prend aucune valeur en paramètre et qui met à jour un attribut.
À la demande
Le principe de cette approche est de faire en sorte de ne mettre à jour les valeurs qu'au moment où on les demande. Seules les entrées sont sauvegardées en mémoire et les sorties sont recalculées à chaque appel[11].
Cache avec invalidation
Recalculer les sorties à chaque fois qu'on les demande est trop inefficace, surtout lorsqu'on travaille avec de grosses données ou si les calculs sont coûteux. Le but de cette approche est d'utiliser une sorte de cache qui permet d'éviter de recalculer inutilement certaines valeurs. Par conséquent, dès qu'on modifie une donnée alors on invalide de manière que dès qu'on demandera la valeur, on sera obligé de recalculer. S'il n'y a eu aucune invalidation, alors on retourne la donnée du cache[12].
Suivre les dépendances
Il y a ici, deux approches différentes :
- On peut suivre les dépendances et utiliser l'approche à la demande en même temps : Dès qu'on demande une valeur, on va remonter dans les dépendances pour calculer et retourner la bonne valeur[13].
- On peut suivre les dépendances et utiliser l'approche du cache : Dès qu'on demande une valeur, on la retourne. Si on modifie une valeur, on fait en sorte que tout soit à jour en utilisant les dépendances pour minimiser les mises à jour[13].
Mises à jour incrémentales
Pour des grosses structures de données, ou des données assez importantes, le fait d'écraser entièrement les données pour les mettre à jour est trop coûteux.
Au lieu de tout recalculer, cette approche consiste à bien cibler les données à mettre à jour pour ne se contenter de ne calculer à nouveau que ce qui a changé réellement[14].
Accumulation des changements
Cette approche consiste à sauvegarder toutes les mises à jour (tous les changements de valeur) et d'attendre la demande d'une valeur pour appliquer toutes les mises à jour d'un coup associée à cette valeur[15].
Programmation réactive fonctionnelle
La programmation réactive fonctionnelle est une approche de la programmation réactive[16].
Exemples d'utilisation
- Les tableurs utilisent notamment ce style de programmation pour garder la cohérence des cellules. Ainsi une cellule contenant "= A1 + 10" sera automatiquement recalculée si A1 est modifiée.
- Ce paradigme est aussi utile dans le cadre d'une architecture MVC pour mettre la vue à jour lorsque le modèle est modifié (ou inversement)[1].
- Il est également utilisé dans des applications temps-réel, par exemple avec le framework Meteor[17].
- La programmation réactive est aussi utilisée dans les interfaces graphiques, pour mettre à jour les coordonnées de la souris en temps réel[1].
- Peut être utilisé pour des applications web, c'est-à-dire, pour répondre à des signaux provenant du réseau[18].
Bibliographie
- (en) Guido Salvaneschi, Alessandro Margara et Giordano Tamburrelli, Reactive Programming: a Walkthrough, , 2 p.
- (en) Guido Salvaneschi et Mira Mezini, Towards Reactive Programming for Object-oriented, , 36 p.
- (en) Ingo Maier et Martin Odersky, Deprecating the Observer Pattern with Scala.React, 20 p.
- Frederic Dabrowski, Programmation Réactive Synchrone, Langage et Contrôle des Ressources, 119 p.
- (en) ENGINEER BAINOMUGISHA, ANDONI LOMBIDE CARRETON, TOM VAN CUTSEM, STIJN MOSTINCKX et WOLFGANG DE MEUTER, A Survey on Reactive Programming, 35 p.
- (en) Ingo Maier, Tiark Rompf et Martin Odersky, Deprecating the Observer Pattern, 18 p.
- (en) Alan Jeffrey, Functional Reactive Programming with Liveness Guarantees, , 11 p.
Liens externes
Jonas Bonér, Dave Farley, Roland Kuhn et Martin Thompson, « Le Manifeste Réactif », (consulté le ).
Références
- ↑ a b c d e f g h i j k l m et n Guido Salvaneschi, Alessandro Margara et Giordano Tamburrelli 2015, p. 1.
- ↑ a et b Ingo Maier Martin Odersky, p. 1.
- ↑ a b c et d Guido Salvaneschi et Mira Mezini 2013, p. 1.
- ↑ a et b Ingo Maier et Tiark Rompf Martin Odersky, p. 2.
- ↑ a et b Ingo Maier Martin Odersky, p. 2.
- ↑ « Le Manifeste Réactif », sur reactivemanifesto.org (consulté le ).
- ↑ Frederic Dabrowski, p. 11.
- ↑ a b c d e et f « ReactiveX », sur reactivex.io (consulté le )
- ↑ ENGINEER BAINOMUGISHA, ANDONI LOMBIDE CARRETON et TOM VAN CUTSEM STIJN MOSTINCKX, p. 32.
- ↑ ENGINEER BAINOMUGISHA, ANDONI LOMBIDE CARRETON et TOM VAN CUTSEM STIJN MOSTINCKX, p. 2.
- ↑ Guido Salvaneschi et Mira Mezini 2013, p. 3.
- ↑ Guido Salvaneschi et Mira Mezini 2013, p. 4.
- ↑ a et b Guido Salvaneschi et Mira Mezini 2013, p. 5.
- ↑ Guido Salvaneschi et Mira Mezini 2013, p. 6.
- ↑ Guido Salvaneschi et Mira Mezini 2013, p. 7.
- ↑ Alan Jeffrey 2013, p. 1.
- ↑ Guido Salvaneschi, Alessandro Margara et Giordano Tamburrelli 2015, p. 2.
- ↑ ENGINEER BAINOMUGISHA, ANDONI LOMBIDE CARRETON et TOM VAN CUTSEM STIJN MOSTINCKX, p. 30.
v · m Paradigmes de programmation | |||||
---|---|---|---|---|---|
Impérative |
| ||||
Déclarative |
| ||||
Métaprogrammation | |||||
Autres |
| ||||
Comparaison des langages de programmation multi-paradigmes |
Portail de la programmation informatique
Portail de l'informatique théorique