Modèles architecturaux en TypeScript : comment arrêter d'écrire des "spaghettis" et commencer à vivre
\n\nAvez-vous déjà ouvert un projet TypeScript écrit il y a six mois et réalisé avec horreur que vous ne pouviez pas comprendre vos propres classes ? Ou peut-être ajoutiez-vous une nouvelle fonctionnalité et l'application plantait à trois endroits inattendus ? Cela vous parle ? Bienvenue au club des développeurs qui ont un jour décidé que "ça ira" — et l'ont payé cher.
\n\nTypeScript n'est pas simplement une surcouche de JavaScript avec des types. C'est un outil puissant qui permet de construire des systèmes complexes, évolutifs et maintenables. Mais les types seuls ne vous sauveront pas du chaos architectural. Vous avez besoin de modèles architecturaux — des recettes éprouvées par le temps qui vous aideront à organiser le code pour qu'il soit compréhensible, testable et flexible.
\n\nDans cet article, nous allons analyser cinq modèles architecturaux TypeScript clés, les plus couramment utilisés dans les projets réels. Vous verrez des exemples de code, comprendrez où et pourquoi appliquer chaque modèle, et pourrez les intégrer immédiatement dans votre travail. C'est parti !
\n\n1. MVC (Model-View-Controller) — un grand classique
\n\nCommençons par le modèle le plus connu. MVC en TypeScript consiste à diviser l'application en trois couches :
\n\n- \n
- Model — les données et la logique métier. \n
- View — l'affichage (UI). \n
- Controller — l'élément de liaison, traite les entrées utilisateur. \n
Dans les applications TypeScript, MVC est particulièrement intéressant car nous pouvons typer strictement toutes les couches, éliminant les erreurs dès la compilation.
\n\nExemple : une simple liste de tâches (Todo List)
\n\n// Model\ninterface Task {\n id: number;\n title: string;\n completed: boolean;\n}\n\nclass TaskModel {\n private tasks: Task[] = [];\n\n addTask(title: string): Task {\n const newTask: Task = {\n id: Date.now(),\n title,\n completed: false,\n };\n this.tasks.push(newTask);\n return newTask;\n }\n\n getTasks(): Task[] {\n return this.tasks;\n }\n}\n\n// View\nclass TaskView {\n render(tasks: Task[]): void {\n console.clear();\n tasks.forEach(task => {\n console.log(`${task.id}: [${task.completed ? 'x' : ' '}] ${task.title}`);\n });\n }\n}\n\n// Controller\nclass TaskController {\n constructor(\n private model: TaskModel,\n private view: TaskView\n ) {}\n\n addTask(title: string): void {\n this.model.addTask(title);\n this.view.render(this.model.getTasks());\n }\n\n showAll(): void {\n this.view.render(this.model.getTasks());\n }\n}\n\n// Utilisation\nconst model = new TaskModel();\nconst view = new TaskView();\nconst controller = new TaskController(model, view);\ncontroller.addTask('Apprendre les modèles TypeScript');\ncontroller.addTask('Écrire un article');\n\nPourquoi est-ce génial ? Vous pouvez changer la View (par exemple, passer de la console à React) sans toucher au Model. Vous pouvez tester la logique métier séparément de l'UI. L'architecture d'une application TypeScript devient transparente.
\n\n2. Observer — le roi des événements
\n\nSi votre application vit grâce aux événements (clics, réponses serveur, minuteurs), le modèle Observer TypeScript est votre meilleur ami. Il permet à certains objets (abonnés) de surveiller les changements d'un autre objet (éditeur).
\n\nExemple : système de notifications
\n\n// Interface de l'observateur\ninterface Observer {\n update(data: string): void;\n}\n\n// Éditeur\nclass NewsPublisher {\n private observers: Observer[] = [];\n\n subscribe(observer: Observer): void {\n this.observers.push(observer);\n }\n\n unsubscribe(observer: Observer): void {\n this.observers = this.observers.filter(obs => obs !== observer);\n }\n\n publishNews(news: string): void {\n this.observers.forEach(obs => obs.update(news));\n }\n}\n\n// Observateur concret : notification par email\nclass Em