Voir le flux RSS

Le Blog d'un Ninja codeur

[Actualité] Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript

Noter ce billet
par , 04/09/2016 à 22h39 (1247 Affichages)
Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript

Nom : TypeScript.png
Affichages : 1891
Taille : 1,8 Ko

Introduction

Le design pattern (patron de conception) Observateur est omniprésent de nos jours. Que ce soit en programmation événementielle, avec les architectures MV* style Angular et la problématique du data-binding, la programmation dite reactive ou encore en conjonction avec l'approche Entity-Component-Systems.

Ce billet n'a pas pour ambition de vous expliquer en détail le design pattern Observateur, ce pourrait faire l'objet d'un article indépendant, mais de vous présenter une petite bibliothèque de mon cru qui implémente ce design pattern de façon légèrement différente de ce qui est usuel de rencontrer.

Cette bibliothèque nommée Paon (comme l'oiseau) a la modeste ambition de répondre à différents objectifs :
  • Être sans dépendance
  • Être simple à utiliser
  • Être généraliste et non rattaché à une architecture (notamment MV*)
  • Ne pas contraindre la structure des objets à observer via l'héritage que ce soit par classe ou par interface
  • Faire appel à des observateurs sous la forme de fonctions


De ces contraintes a résulté la bibliothèque Paon qui en est à sa version 0.2.2 et que vous pouvez récupérer sur mon compte GitHub ou via npm.

A noter que bien qu'étant rédigée en TypeScript, cette bibliothèque peut être utilisée par des applications JavaScript (cf. répertoire dist/).

Cette bibliothèque est en open source sous licence MIT.

En espérant que cette bibliothèque pourra vous être utile. N'hésitez pas à me faire part de vos retours d'expérience et à la partager autour de vous.


Ce qui suit est la traduction en français de sa description en anglais.

Paon

Un composant Observateur en TypeScript/JavaScript.

  • Pas d'héritage requis.
  • L'observable est juste un composant à l'intérieur d'un objet.
  • Les observateurs sont juste des fonctions.



Installation
npm install paon

Pour compiler le source TypeScript en JavaScript, vous pourriez avoir besoin d'installer le compilateur TypeScript :
npm install -g typescript

Pour générer la version JavaScript minifiée lors du build, vous pourriez avoir besoin d'installer uglifyjs :
npm install -g uglifyjs


Build
Les fichiers résultants sont créés dans le répertoire dist/.

Build complet (compilation et minification):
npm run build

Simple compilation (pas de minification):
npm run compile


Utilisation

Toutes les constantes, interfaces, classes et fonctions sont accessibles au sein de l'espace de nommage Paon.

Exemple simple

Voici un exemple simple où nous ajoutons le composant observable à l'intérieur de notre classe Subject :

Code typescript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <reference path="paon.d.ts" />
 
class Subject {
	private name: string;
	observable: Paon.Observable; // Composant Observer Pattern
 
	constructor(name: string) {
		this.name = name;
		this.observable = new Paon.Observable(); // Instanciation / Initialisation
	}
 
	changeName(name: string): string {
		this.name = name;
		this.observable.notifyObservers("nameChanged"); // Un message est envoyé aux observateurs
		return this.name;
	}
}
 
function onNameChanged() {
	alert("Name has changed");
}
 
let subject = new Subject("Penelope");
 
subject.observable.addObserver("nameChanged", onNameChanged); // La fonction onNameChanged() souscrit aux messages "nameChanged" du sujet
 
subject.changeName("Melissa");
// Une popup d'alerte apparaît: "Name has changed"

Ci-dessus, dans la classe Subject, la méthode changeName() enverra un message "nameChanged" aux observateurs de l'instance.
Après l'instanciation de Subject, la fonction onNameChanged() souscrit aux messages "nameChanged" du sujet.
Par conséquent, lorsque changeName() est appelée, une popup d'alerte apparaît.

Comme nous pouvons le voir, avec un tel pattern, aucun héritage via extends ou implements n'est requis. Juste une simple composition.

Exemple avec des données complémentaires

Nous pouvons envoyer des données complémentaires aux observateurs comme nous pouvons le voir ci-dessous :

Code typescript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <reference path="paon.d.ts" />
 
class Subject {
	private name: string;
	observable: Paon.Observable; // Composant Observer Pattern
 
	constructor(name: string) {
		this.name = name;
		this.observable = new Paon.Observable(); // Instanciation / Initialisation
	}
 
	changeName(name: string): string {
		this.name = name;
		this.observable.notifyObservers("nameChanged", { data: name }); // Un message avec une donnée complémentaire est envoyé aux observateurs
		return this.name;
	}
}
 
function onNameChanged(msg: { data: string }) {
	alert("Name has changed into " + msg.data);
}
 
let subject = new Subject("Penelope");
 
subject.observable.addObserver("nameChanged", onNameChanged); // La fonction onNameChanged() souscrit aux messages "nameChanged" du sujet
 
subject.changeName("Melissa");
// Une popup d'alerte apparaît: "Name has changed into Melissa"

Le paramètre msg dans la function onNameChanged() contient la donnée complémentaire que nous avons envoyé via la méthode changeName(). Ici, c'est un objet avec la propriété data, mais ça pourrait être n'importe quoi.

Importation de module

Cette bibliothèque peut également être importée en tant que module avec l'instruction import :

Code typescript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Paon from "./paon"; // Emplacement du fichier de déclaration .d.ts
 
class Subject {
	private name: string;
	observable: Paon.Observable; // Composant Observer Pattern
 
	constructor(name: string) {
		this.name = name;
		this.observable = new Paon.Observable(); // Instanciation / Initialisation
	}
 
	changeName(name: string): string {
		this.name = name;
		this.observable.notifyObservers("nameChanged", { data: name }); // Un message avec une donnée complémentaire est envoyé aux observateurs
		return this.name;
	}
}
 
function onNameChanged(msg: { data: string }) {
	alert("Name has changed into " + msg.data);
}
 
let subject = new Subject("Penelope");
 
subject.observable.addObserver("nameChanged", onNameChanged); // La fonction onNameChanged() souscrit aux messages "nameChanged" du sujet
 
subject.changeName("Melissa");
// Une popup d'alerte apparaît: "Name has changed into Melissa"

Seule l'instruction import remplace la référence des exemples précédents. A part cela, le reste du code est identique.


Documentation de l'API

Ajoute un observateur à un type de message (similaire à la fonction DOM addEventListener()) :

Paon.Observable.addObserver(type: string, observer: Observer): void;


Retire un observateur d'un type de message (similaire à la fonction DOM removeEventListener()) :

Paon.Observable.removeObserver(type: string, observer: Observer): void;

Retire tous les observateurs d'un type de message :

Paon.Observable.removeObserversType(type: string): void;


Envoie une message aux observateurs (similaire à la fonction DOM dispatchEvent()) :

Paon.Observable.notifyObservers(type: string, msg?: any): void;


Contributeurs
yahiko


Licence
MIT

Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Viadeo Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Twitter Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Google Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Facebook Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Digg Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Delicious Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog MySpace Envoyer le billet « Paon : le design pattern Observateur sans héritage en TypeScript / JavaScript » dans le blog Yahoo

Commentaires

  1. Avatar de SylvainPV
    • |
    • permalink
    Pour ceux qui n'utilsient pas TypeScript, j'ai l'habitude d'utiliser cet équivalent en ES6 : https://gist.github.com/datchley/37353d6a2cb629687eb9 ; je crois que c'est grosso-modo la même chose, la seule différence notable que j'ai vu avec Paon est l'utilisation d'une Map pour stocker les listeners.

    Un truc que je trouve très utile et qui n'est pas présent sur ces deux implémentations, c'est de faire retourner l'observer par la méthode addObserver. Ça permet d'ajouter des listeners avec des fonctions anonymes et d'obtenir une référence en une seule ligne, c'est très pratique à l'usage et ça coûte rien à ajouter
  2. Avatar de yahiko
    • |
    • permalink
    En effet, l'implémentation est quasiment identique sauf la Map. Jusqu'à présent, je n'ai jamais encore eu le besoin d'identifier mes événements/messages autrement qu'avec des chaînes, et les autres fonctionnalités d'une Map, comme connaître le nombre d'éléments ne me semble pas indispensable dans ce cas. Mais c'est possible que je fasse évoluer Paon dans ce sens pourquoi pas. Merci pour la référence.

    Sinon, je viens de rajouter ton idée de renvoyer l'observateur par addObserver(), c'est en effet plus pratique pour stocker et réutiliser l'observateur s'il s'agit d'une fonction anonyme. Je publierai la mise à jour demain probablement.

    Au plaisir
    Mis à jour 05/09/2016 à 22h24 par yahiko