Observer, Factory, Singleton, State Machine, Immutabilité
Le pattern le plus utilisé en JS. Un objet notifie des "abonnés" quand il change. C'est exactement addEventListener — mais personnalisé.
class EventEmitter {
#listeners = {};
on(event, fn) {
if (!this.#listeners[event]) this.#listeners[event] = [];
this.#listeners[event].push(fn);
return this; // chaînable
}
off(event, fn) {
if (!this.#listeners[event]) return;
this.#listeners[event] = this.#listeners[event].filter(l => l !== fn);
}
emit(event, ...args) {
this.#listeners[event]?.forEach(fn => fn(...args));
}
}
// Usage
const bus = new EventEmitter();
bus.on('user:login', user => console.log(`Bienvenue ${user.nom}`));
bus.on('user:login', user => localStorage.setItem('user', JSON.stringify(user)));
bus.emit('user:login', { nom: "Alice", role: "admin" });
// Bienvenue Alice + sauvegarde dans localStorage
useState notifie les composants qui re-rendent.watch() observe les changements réactifs.
Créer un espace de noms avec des données privées — sans class.
const CartManager = (() => {
// Privé — inaccessible de l'extérieur
let articles = [];
function calculerTotal() {
return articles.reduce((acc, a) => acc + a.prix * a.qte, 0);
}
// Public — ce qu'on expose
return {
ajouter(article) {
const existant = articles.find(a => a.id === article.id);
if (existant) existant.qte++;
else articles.push({ ...article, qte: 1 });
},
supprimer(id) {
articles = articles.filter(a => a.id !== id);
},
get total() { return calculerTotal(); },
get items() { return [...articles]; }, // copie — pas la ref interne
};
})(); // IIFE — s'exécute immédiatement
CartManager.ajouter({ id: 1, nom: "Laptop", prix: 899 });
console.log(CartManager.total); // 899
// articles → inaccessible directement
Une fonction qui crée et retourne des objets — sans new.
function creerUtilisateur(nom, role = 'user') {
if (!nom || nom.length < 2) throw new Error("Nom invalide");
let _tentativesConnexion = 0;
return {
nom,
role,
connecter(mdp) {
_tentativesConnexion++;
if (_tentativesConnexion > 3) throw new Error("Compte bloqué");
return mdp === "secret";
},
get tentatives() { return _tentativesConnexion; },
};
}
const alice = creerUtilisateur("Alice", "admin");
alice.connecter("mauvais"); // false, tentatives=1
alice.connecter("secret"); // true, tentatives=2
console.log(alice._tentativesConnexion); // undefined — privé !
Une seule instance dans toute l'application.
class ConfigApp {
static #instance = null;
#config = {
theme: 'dark',
langue: 'fr',
apiUrl: 'https://api.example.com',
};
static getInstance() {
if (!ConfigApp.#instance) {
ConfigApp.#instance = new ConfigApp();
}
return ConfigApp.#instance;
}
get(cle) { return this.#config[cle]; }
set(cle, val) { this.#config[cle] = val; }
getAll() { return { ...this.#config }; }
}
const config1 = ConfigApp.getInstance();
const config2 = ConfigApp.getInstance();
config1.set('theme', 'light');
console.log(config2.get('theme')); // "light" — même instance !
console.log(config1 === config2); // true
Gère les transitions d'état de façon explicite. Essentiel pour les formulaires, les loaders, etc.
const etats = {
IDLE: { label: 'Prêt', suivants: ['CHARGEMENT'] },
CHARGEMENT: { label: 'Chargement', suivants: ['SUCCES', 'ERREUR'] },
SUCCES: { label: 'Succès', suivants: ['IDLE'] },
ERREUR: { label: 'Erreur', suivants: ['IDLE', 'CHARGEMENT'] },
};
class StateMachine {
#etat = 'IDLE';
get etat() { return this.#etat; }
transition(nouvelEtat) {
const autorise = etats[this.#etat].suivants;
if (!autorise.includes(nouvelEtat)) {
throw new Error(`Transition ${this.#etat} → ${nouvelEtat} interdite`);
}
this.#etat = nouvelEtat;
return this;
}
}
const machine = new StateMachine();
machine.transition('CHARGEMENT'); // OK
machine.transition('SUCCES'); // OK
machine.transition('SUCCES'); // Error ! SUCCES → SUCCES non autorisé
Principe fondamental de React et Redux : ne jamais muter l'état, toujours créer du nouveau.
// ❌ Mutation — React ne voit pas le changement
const etat = { user: null, items: [] };
etat.items.push("nouveau"); // modifie en place
// ✅ Immutable — crée un nouvel objet
const nouvelEtat = {
...etat,
items: [...etat.items, "nouveau"],
};
// ✅ Mise à jour d'un item dans un tableau
const items = [{ id: 1, val: "a" }, { id: 2, val: "b" }];
const updated = items.map(item =>
item.id === 1 ? { ...item, val: "z" } : item
);
// items non modifié, updated contient la nouvelle version
| Pattern | Quand l'utiliser |
|---|---|
| Observer | Notifier plusieurs parties d'un changement |
| Module | Encapsuler des données privées dans un namespace |
| Factory | Créer des objets avec logique de construction |
| Singleton | Une seule config / store global |
| State Machine | Contrôler les transitions d'état |
| Immutabilité | Toujours avec React/Redux/Vue |
Implémente Observer, Factory et State Machine dans une mini-application architecturée.
Télécharge les fichiers, ouvre-les dans ton éditeur et travaille directement dedans.
4 exercices sur EventEmitter, store réactif, Factory avec validation et State Machine.
Corrections complètes. À consulter après avoir essayé.