interface Channel { send(to: string, message: string): Promise<void>;}class SmsChannel implements Channel { async send(to: string, message: string) { /* API SMS */ }}class Notifier { constructor(private channels: Channel[]) {} async notify(to: string, message: string) { await Promise.all(this.channels.map((c) => c.send(to, message))); }}// On assemble les comportements au lieu d'empiler des sous-classesconst notifier = new Notifier([new SmsChannel(), new EmailChannel()]);Explication
Avec l'héritage, on finit vite avec des classes comme SmsEtEmailNotifierAvecJournal. La composition consiste à assembler de petits objets interchangeables qui respectent un même contrat (une interface).
Ajouter un canal (WhatsApp, push…) revient à écrire une nouvelle classe, sans toucher au
Notifier.Tester devient simple : on passe un faux canal au
Notifier.Changer de comportement à l'exécution est possible : la liste des canaux peut venir de la configuration.
L'héritage reste utile pour une vraie relation « est un » stable (un Chien est un Animal). Pour partager du comportement, préférez « a un ».

