Aller au contenu principal

Eidos dans l'écosystème TypeScript

Les design patterns du GoF ne sont pas obsolètes et les problèmes qu'ils résolvent sont toujours d'actualité. En fait, ils avaient juste besoin d'un léger rafraîchissement pour coller aux stacks TypeScript modernes : composable et fonctionnel, en s'éloignant de la POO (classes, héritage, …). Même le Singleton, souvent catalogué comme un anti-pattern en POO, se révèle être un bon atout en TypeScript fonctionnel, là où il se résume à un const au niveau du module.

Pithos est la seule bibliothèque TypeScript qui fournit les 23 design patterns du GoF dans un style fonctionnel. Certaines bibliothèques se concentrent sur leur apprentissage sous la forme d'exemples POO. D'autres utilisent un ou deux patterns isolément. Eidos est la première à couvrir les 23 avec une API unifiée, composable et agnostique de tout framework. Cette page explique pourquoi c'est important.


Ce qui existe aujourd'hui

Dans l'écosystème JS/TS, on retrouvera les design patterns sous trois formes. Chacune d'elles résout quelque chose, mais jamais tout à la fois.

  • Sites et repositories éducatifs. Des projets comme refactoring.guru, torokmark/design_patterns_in_typescript ou design-patterns-for-humans couvrent les 23 patterns, mais jamais sous la forme d'une bibliothèque installable dans son propre projet pour déployer ensuite en production. Et en plus, ils présentent les patterns par l'intermédiaire de la POO (classes et héritage), alors que les stacks TypeScript modernes privilégient de plus en plus les approches composables et fonctionnelles.

  • Bibliothèques spécialisées. L'écosystème offre d'excellentes solutions mono-pattern : RxJS, mitt ou nanoevents pour Observer ; XState ou Robot pour les machines à états ; Immer pour Command/undo-redo ; Ramda pour la composition de fonctions. Certaines bibliothèques comme Zustand combinent plusieurs patterns (State, Observer, Command) mais restent liées à React. Chacune couvre bien sa niche, mais aucune ne vise à fournir un ensemble complet et indépendant de tout framework.

  • Écosystèmes FP. Des bibliothèques comme fp-ts, Effect ou purify-ts fournissent des abstractions fonctionnelles qui chevauchent indirectement certains patterns GoF. Mais aucune ne se donne pour objectif de couvrir les 23. La couverture est incidente.

Où se situe Eidos

Eidos occupe un espace qu'aucune des trois catégories ci-dessus ne couvre : une bibliothèque prête pour la production, tree-shakable, qui couvre chaque pattern GoF avec une API fonctionnelle cohérente.


Le problème de la fragmentation

Couvrir seulement cinq patterns avec des bibliothèques spécialisées, ça veut dire cinq packages à installer, cinq styles d'API, cinq sites de documentation et cinq modèles qui pourraient pourtant être utilisés conjointement :

npm install rxjs           # Observer
npm install xstate # State
npm install ramda # Decorator / composition
npm install immer # Memento / Command
npm install iter-tools # Iterator

Ces bibliothèques sont excellentes dans leur périmètre, mais c'est un peu dommage de devoir installer autant de packages qui ne sont pas prévus spécifiquement pour fonctionner ensemble, qui augmentent la surface de maintenance et qui en plus multiplient le risque de supply chain : chaque dépendance est une vulnérabilité potentielle.


Ce qu'Eidos fait différemment

Indépendant de tout framework

La plupart des bibliothèques spécialisées sont liées à un écosystème spécifique. Par exemple :

  • RxJS est profondément intégré à Angular
  • Zustand est conçu spécifiquement pour React.
  • XState a des bindings spécifiques par framework.

Si vous changez de framework, ou si vous travaillez sur un projet backend Node.js, ces bibliothèques pourraient apporter de la friction ou risqueraient d'introduire un couplage inutile.

Eidos n'est pas propre à une technologie ou un framework. Il produit des fonctions pures et des données simples. Vous pouvez l'utiliser en React, Angular, Vue, Svelte, un outil CLI, une fonction serverless ou un projet TypeScript vanilla. Les patterns fonctionnent de façon identique quelle que soit la stack, parce que la seule chose dont ils dépendent, c'est TypeScript !

Un chemin d'import, une philosophie

Chaque pattern suit les mêmes conventions : les fonctions retournent des données simples, l'état est immuable, la configuration utilise des records typés, la composition utilise des fonctions d'ordre supérieur.

import { createStrategies } from "@pithos/core/eidos/strategy/strategy";
import { createMachine } from "@pithos/core/eidos/state/state";
import { createChain } from "@pithos/core/eidos/chain/chain";
import { createObservable } from "@pithos/core/eidos/observer/observer";
import { createBuilder } from "@pithos/core/eidos/builder/builder";

Pas de classes. Pas d'héritage. Pas de méthodes abstraites.

Transparence vis-à-vis du langage

Eidos exporte délibérément les 23 patterns, y compris les cinq que TypeScript rend triviaux. Ces cinq sont marqués @deprecated, ce qui sert deux objectifs :

  • La bibliothèque reste une référence complète et homogène pour tous les patterns GoF,
  • En cherchant un design pattern tel que "factory method" ou "visitor", l'IDE indique simplement la façon d'utiliser TypeScript nativement plutôt que de rajouter une fonction passe-plat qui n'apporterait rien.
PatternCe que votre IDE vous montre
Factory Method"Passez la fonction factory en paramètre. C'est de l'injection de dépendances."
Bridge"Passez l'implémentation comme paramètre de fonction."
Facade"Écrivez une fonction qui appelle d'autres fonctions."
Visitor"Utilisez un switch sur une union discriminée."
Interpreter"Définissez une union discriminée pour l'AST et une fonction eval récursive."

Infrastructure partagée avec Pithos

Afin de ne pas dupliquer inutilement le code source et ajouter du poids mort dans le bundle, quatre designs patterns sont des ré-exports d'Arkhe :

PatternFonction Arkhe
PrototypedeepClone()
Singletononce()
Flyweightmemoize()
Proxymemoize(), throttle(), debounce(), lazy(), guarded()

Chaque pattern a une démo interactive

Chaque design pattern est accompagné d'une page dédiée qui inclut le problème qu'il résout, la solution Eidos, et une démo interactive que vous pouvez essayer dans le navigateur, avec son code source téléchargeable.

Découvrir les démos
State demo

State
Machine à états finis avec transitions et événements typés. Un tableau de scores de tennis pour Roland Garros où la boucle Deuce/Avantage est encodée dans les transitions, pas dans des conditions.

createMachine()

Strategy demo

Strategy
Interchanger des algorithmes à l'exécution par clé. Un calculateur de prix pour un e-shop cosmétique avec des stratégies de réduction interchangeables (Standard, Club, Saisonnier, Coffret).

createStrategies(), safeStrategy(), withFallback(), withValidation()

Observer demo

Observer
Émetteur d'événements pub/sub typé. Un tableau de bord de trading où graphique, alertes et portefeuille réagissent indépendamment aux variations de prix.

createObservable()

Command demo

Command
Encapsuler des actions avec undo/redo. Un tableau Kanban où chaque glisser-déposer est une commande réversible avec un historique lisible et un replay.

undoableState(), createReactiveCommandStack()

Iterator demo

Iterator
Séquences lazy avec next() basé sur Option. Un navigateur Pokédex avec trois stratégies de parcours interchangeables sur 151 Pokémon.

createIterable(), lazyRange(), iterate()

Builder demo

Builder
Construction étape par étape avec une API fluide. Un constructeur de graphiques où activer/désactiver des étapes met à jour la visualisation en temps réel.

createBuilder(), createValidatedBuilder()

Decorator demo

Decorator
Empiler des comportements sans modifier la fonction de base. Un pipeline d'analyse ADN où l'on active/désactive des décorateurs de filtre qualité, cache, retry et chronométrage.

decorate(), before(), after(), around()

Chain demo

Chain of Responsibility
Pipeline de handlers qui peuvent passer la main ou court-circuiter. Un simulateur de middleware HTTP avec auth, rate-limit, validation et logging activables.

createChain(), safeChain()

Mediator demo

Mediator
Centraliser la communication entre composants. Un tableau de bord de vols DGAC où les panneaux météo, vols et piste communiquent via un hub d'événements typé.

createMediator()

Memento demo

Memento
Capturer et restaurer des snapshots d'état. Un éditeur photo avec filtres où chaque modification crée une miniature de snapshot vers laquelle on peut revenir.

createHistory()

Template demo

Template Method
Définir le squelette d'un algorithme avec des étapes redéfinissables. Un constructeur de CV où les profils Développeur, Designer et Manager redéfinissent des étapes spécifiques.

templateWithDefaults()

Abstract Factory demo

Abstract Factory
Créer des familles d'objets liés. Un kit UI multiplateforme où basculer entre iOS/Android/Web re-habille l'ensemble du formulaire d'un coup.

createAbstractFactory()

Composite demo

Composite
Structures arborescentes avec traitement uniforme feuille/branche. Un explorateur de fichiers où la taille des dossiers est calculée récursivement via fold().

leaf(), branch(), fold(), map(), flatten(), find()

Adapter demo

Adapter
Faire fonctionner ensemble des API incompatibles. Une application cartographique qui normalise deux API open data françaises (bornes de recharge + stations-service) en un format uniforme.

adapt(), createAdapter()

Proxy demo

Proxy
Contrôler l'accès avec cache, rate-limiting et fallback. Une API LLM simulée où les cache hits économisent de l'argent et les rate limits protègent le fournisseur.

memoize(), throttle(), lazy(), guarded()

Singleton demo

Singleton
Initialisation lazy, même instance à chaque appel. Trois services (Database, Cache, Logger) où la première requête est lente et les suivantes instantanées.

once()

Prototype demo

Prototype
Cloner des objets sans références partagées. Un éditeur de configuration où le deep clone préserve l'original intact tandis que le shallow copy laisse fuiter les mutations.

deepClone(), deepCloneFull()

Flyweight demo

Flyweight
Partager l'état commun pour réduire la mémoire. Un éditeur de texte où les objets de style sont mutualisés via memoize, avec 96 % d'économie mémoire.

memoize()

Factory Method demo

Factory Method
Déléguer la création à des fonctions injectées. Un expéditeur de notifications où le même appel sendNotification() produit des notifications Email, SMS, Push ou Slack.

Pattern absorbé par le langage (injection de dépendances)

Visitor demo

Visitor
Ajouter des opérations sans modifier les objets. Un constructeur d'emails où la même liste de blocs produit un rendu Aperçu, HTML, Texte brut ou Audit d'accessibilité.

Pattern absorbé par le langage (switch sur union discriminée)

Bridge demo

Bridge
Découpler deux axes qui varient indépendamment. Un visualiseur musical avec 3 sources audio × 5 renderers = 15 combinaisons en un seul appel de fonction.

Pattern absorbé par le langage (passer des fonctions en paramètre)

Facade demo

Facade
Interface simplifiée vers un sous-système complexe. Une démo de requête API montrant 6 étapes manuelles vs un seul appel fetchUser().

Pattern absorbé par le langage (c'est juste une fonction)

Interpreter demo

Interpreter
Évaluer des expressions ou des DSL. Un interpréteur Markdown complet avec aperçu en direct, visualisation de l'AST et défilement synchronisé.

Pattern absorbé par le langage (union discriminée + eval récursif)


Eidos vs bibliothèques dédiées

Pour certains patterns, une lib dédiée ira plus loin qu'Eidos. C'est normal et c'est même voulu : Eidos couvrira 80-90% des cas les plus courants, mais pour des cas limites, certaines bibliothèques spécialisées prendront le relais.

PatternQuand Eidos suffitQuand passer à une lib dédiée
ObserverPub/sub typé simpleTraitement de flux complexe (RxJS)
StateMachine à états finis avec transitions typéesStatecharts hiérarchiques, acteurs, visualiseur (XState)
IteratorSéquences lazy, opérateurs de baseOpérateurs d'itération async étendus (IxJS)
CommandUndo/redo avec historique d'actionsGestion d'état complète avec devtools (Zustand)
DecoratorHooks before/after/aroundLib utilitaire FP complète (Ramda)

Patterns sans alternative

Huit patterns n'ont tout simplement aucune lib TypeScript fonctionnelle standalone. Aujourd'hui, c'est soit on l'implémente à la main, soit on utilise Eidos :

  • Abstract Factory
  • Builder (générique, pas spécifique à un domaine)
  • Chain of Responsibility (standalone, pas du middleware de framework)
  • Adapter (wrapper de fonction typé)
  • Mediator (hub d'événements typé)
  • Memento (historique de snapshots)
  • Template Method
  • Composite (arbre typé avec fold/map)

Eidos complète, il ne remplace pas

Eidos n'est pas un concurrent direct des bibliothèques spécialisées. Si le framework fournit déjà une primitive réactive (Observables RxJS, Signals Angular, signals Solid), on l'utilise. Eidos s'efface. Si on a besoin de statecharts complets avec un inspecteur visuel, XState est le bon outil. Si on a besoin d'un système d'effets complet avec concurrence structurée, Effect est le bon outil.

Eidos comble les trous entre ces solutions spécialisées : les patterns qui n'ont pas de lib dédiée, ceux qu'on implémente à la main faute de mieux, et ceux qui sont tellement simples en TypeScript fonctionnel qu'un rappel d'une ligne dans l'IDE suffit.

On peut utiliser Eidos à côté de n'importe laquelle de ces libs. Un pattern ou vingt, chacun est tree-shakable indépendamment, et les patterns qui chevauchent Arkhe n'ajoutent aucun poids au bundle.


Pour aller plus loin