Chapitre 18
18. Exercices - Design Patterns
18. Exercices - Design Patterns
Exercices de Design Patterns - 40 Énoncés
Niveau Débutant (10 exercices)
Exercice 1 : Identifier le Singleton
class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {}
static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
}
Question : Quel pattern est implémenté ci-dessus ? Pourquoi utilise-t-on un constructeur privé ?
Exercice 2 : Identifier l'Observateur
class NewsPublisher {
private subscribers: Subscriber[] = [];
subscribe(s: Subscriber) { this.subscribers.push(s); }
unsubscribe(s: Subscriber) { this.subscribers = this.subscribers.filter(x => x !== s); }
notify(data: string) { this.subscribers.forEach(s => s.update(data)); }
}
Question : Identifiez le pattern. Quels sont les rôles des différentes classes ?
Exercice 3 : Identifier la Stratégie
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardPayment implements PaymentStrategy { /* ... */ }
class PayPalPayment implements PaymentStrategy { /* ... */ }
class PaymentContext {
constructor(private strategy: PaymentStrategy) {}
executePayment(amount: number) { this.strategy.pay(amount); }
}
Question : Expliquez le pattern Stratégie. Comment ajouter un nouveau moyen de paiement ?
Exercice 4 : Identifier l'Adaptateur
interface NewLogger { log(message: string): void; }
class LegacyLogger {
writeLog(text: string): void { /* legacy implementation */ }
}
class LoggerAdapter implements NewLogger {
constructor(private legacy: LegacyLogger) {}
log(message: string): void { this.legacy.writeLog(message); }
}
Question : Quel problème résout l'Adaptateur ? Que se passe-t-il sans lui ?
Exercice 5 : Identifier le Décorateur
interface Coffee { cost(): number; description(): string; }
class SimpleCoffee implements Coffee { /* base implementation */ }
class MilkDecorator implements Coffee {
constructor(private coffee: Coffee) {}
cost() { return this.coffee.cost() + 1.5; }
}
Question : Comment le Décorateur permet-il d'ajouter des fonctionnalités sans modifier la classe de base ?
Exercice 6 : Identifier le Factory Method
abstract class DocumentCreator {
abstract createDocument(): Document;
openDocument() {
const doc = this.createDocument();
doc.open();
}
}
class PdfCreator extends DocumentCreator {
createDocument(): Document { return new PDFDocument(); }
}
Question : Expliquez le Factory Method. Quel est l'avantage par rapport à new PDFDocument() ?
Exercice 7 : Identifier le Composite
interface FileSystemNode {
getName(): string;
getSize(): number;
}
class File implements FileSystemNode { /* ... */ }
class Directory implements FileSystemNode {
private children: FileSystemNode[] = [];
getSize() { return this.children.reduce((sum, c) => sum + c.getSize(), 0); }
}
Question : Pourquoi Directory et File partagent-ils la même interface ?
Exercice 8 : Identifier l'État (State)
interface State {
handle(context: Context): void;
}
class PublishedState implements State {
handle(context: Context) { /* allow publishing */ }
}
class DraftState implements State {
handle(context: Context) { /* allow editing */ }
}
Question : Comment le pattern State simplifie-t-il la gestion des transitions d'état ?
Exercice 9 : Identifier le Template Method
abstract class DataProcessor {
process(): void {
this.loadData();
this.processData();
this.saveData();
}
abstract loadData(): void;
abstract processData(): void;
abstract saveData(): void;
}
Question : Quelle méthode est le template ? Qu'est-ce qui est fixe et variable ?
Exercice 10 : Identifier le Proxy
interface Image {
display(): void;
}
class RealImage implements Image {
constructor(private filename: string) { this.loadFromDisk(); }
display() { /* show image */ }
}
class ProxyImage implements Image {
private realImage: RealImage | null = null;
display() {
if (!this.realImage) this.realImage = new RealImage(this.filename);
this.realImage.display();
}
}
Question : Quel est l'avantage du Proxy dans cet exemple (lazy loading) ?
Niveau Intermédiaire (10 exercices)
Exercice 11 : Implémenter un Builder
Créez un Builder pour construire des objets HttpRequest avec : method, url, headers, body, timeout.
// À compléter
class HttpRequest {
// ...
}
class HttpRequestBuilder {
// ...
}
// Utilisation :
// new HttpRequestBuilder()
// .setMethod('POST')
// .setUrl('/api/users')
// .addHeader('Content-Type', 'application/json')
// .setBody({ name: 'Alice' })
// .setTimeout(5000)
// .build();
Exercice 12 : Implémenter un Observateur
Créez un système d'events avec EventEmitter qui supporte on, off, emit.
class EventEmitter {
// Implémentez on(event, handler), off(event, handler), emit(event, data)
}
// Test :
// const emitter = new EventEmitter();
// emitter.on('user:created', (user) => console.log(user));
// emitter.emit('user:created', { id: 1, name: 'Alice' });
Exercice 13 : Implémenter une Stratégie de tri
Créez un système de tri avec différentes stratégies (QuickSort, MergeSort, BubbleSort).
interface SortStrategy {
sort<T>(items: T[]): T[];
}
class Sorter {
constructor(private strategy: SortStrategy) {}
sort<T>(items: T[]): T[] { return this.strategy.sort(items); }
}
// Implémentez QuickSortStrategy, MergeSortStrategy
Exercice 14 : Implémenter un Décorateur de données
Créez un système de chiffrement/décoration de flux de données.
interface DataSource {
write(data: string): void;
read(): string;
}
// Implémentez FileDataSource, EncryptionDecorator, CompressionDecorator
Exercice 15 : Implémenter un Adaptateur pour une API
// API externe (incompatible)
class ExternalWeatherAPI {
getTemperature(city: string): number { /* en Fahrenheit */ }
}
// Interface attendue
interface WeatherService {
getTemperatureCelsius(city: string): number;
}
// Créez l'Adaptateur
Exercice 16 : Implémenter un Proxy de cache
interface UserService {
getUser(id: number): Promise<User>;
}
class RealUserService implements UserService {
async getUser(id: number): Promise<User> {
// Appel API lent
}
}
// Implémentez CachedUserService avec TTL
Exercice 17 : Implémenter un Factory Simple
Créez une factory pour des objets Notification (Email, SMS, Push).
enum NotificationType { EMAIL, SMS, PUSH }
interface Notification { send(message: string): void; }
class NotificationFactory {
static create(type: NotificationType): Notification {
// À implémenter
}
}
Exercice 18 : Implémenter un Memento
Créez un système d'undo pour un éditeur de texte.
class TextEditor {
private content: string = '';
// Ajoutez save(), restore(memento)
}
class Memento {
constructor(private state: string) {}
getState(): string { return this.state; }
}
class History {
// Pile de mementos
}
Exercice 19 : Implémenter une Chaîne de Responsabilité
Créez un pipeline de validation de requête.
abstract class ValidationHandler {
protected next: ValidationHandler | null = null;
setNext(handler: ValidationHandler): ValidationHandler { this.next = handler; return handler; }
abstract handle(request: Request): boolean;
}
// Implémentez : AuthHandler, RateLimitHandler, BodyValidationHandler
Exercice 20 : Implémenter un Visitor
Créez un visiteur pour exporter des éléments de document en HTML/Markdown.
interface DocumentElement {
accept(visitor: Visitor): void;
}
class Paragraph implements DocumentElement { /* ... */ }
class Image implements DocumentElement { /* ... */ }
class Table implements DocumentElement { /* ... */ }
interface Visitor {
visitParagraph(p: Paragraph): void;
visitImage(i: Image): void;
}
// Implémentez HTMLVisitor, MarkdownVisitor
Niveau Avancé (10 exercices)
Exercice 21 : Refactoring - Remplacer les conditionnels par la Stratégie
// Code à refactorer
class OrderCalculator {
calculateDiscount(order: Order): number {
if (order.customer.type === 'VIP') return order.total * 0.2;
if (order.customer.type === 'LOYAL') return order.total * 0.15;
if (order.customer.type === 'NEW') return order.total * 0.1;
if (order.total > 1000) return order.total * 0.05;
return 0;
}
}
Refactorez avec le pattern Stratégie.
Exercice 22 : Refactoring - Remplacer les switch par le pattern State
class Document {
private status: string = 'DRAFT';
publish(): void {
switch (this.status) {
case 'DRAFT': this.status = 'MODERATION'; break;
case 'MODERATION': this.status = 'PUBLISHED'; break;
case 'PUBLISHED': throw new Error('Already published');
}
}
}
Refactorez avec le pattern State.
Exercice 23 : Refactoring - Extraire un Decorator
class Notifier {
send(message: string): void {
const emailService = new EmailService();
emailService.send(message);
const smsService = new SMSService();
smsService.send(message);
const slackService = new SlackService();
slackService.send(message);
}
}
Refactorez pour permettre de n'envoyer que les notifications souhaitées.
Exercice 24 : Combinaison Factory + Stratégie + Observateur
Créez un système de traitement de fichiers où :
- Factory crée le bon processeur selon le type
- Stratégie définit l'algorithme de traitement
- Observateur notifie les étapes du traitement
Exercice 25 : Refactoring - Anti-pattern God Object
class UserManager {
createUser() { /* ... */ }
deleteUser() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
validateAddress() { /* ... */ }
processPayment() { /* ... */ }
calculateTax() { /* ... */ }
}
Refactorez en séparant les responsabilités avec les patterns appropriés.
Exercice 26 : Architecture - Système de plugins
Concevez un système de plugins en utilisant : Strategy (pour les algorithmes), Factory (pour la création), Observateur (pour les événements).
Exercice 27 : Refactoring - Anti-pattern Spaghetti Code
function processOrder(order: any) {
if (order.type === 'physical') {
// 50 lignes de logistique
} else if (order.type === 'digital') {
// 30 lignes de téléchargement
} else if (order.type === 'subscription') {
// 40 lignes d'abonnement
}
}
Refactorez avec les patterns Template Method, Strategy, ou State.
Exercice 28 : Combinaison Composite + Visitor
Créez une structure arborescente de composants UI avec Composite, et un Visitor pour le rendu (HTML, texte, JSON).
Exercice 29 : Architecture - Microservices avec Saga
Concevez les interactions entre services pour un système e-commerce en utilisant Saga (orchestration) et Domain Events.
Exercice 30 : Implémentation - Circuit Breaker
Implémentez un Circuit Breaker complet avec les 3 états, metrics, et fallback.
Niveau Expert (10 exercices)
Exercice 31 : Architecture - CQRS + Event Sourcing
Concevez un système de gestion de comptes bancaires avec CQRS et Event Sourcing. Commandes : deposit, withdraw, transfer. Requêtes : getBalance, getTransactions.
Exercice 32 : Architecture Hexagonale
Implémentez une architecture hexagonale (ports/adapters) pour un service de réservation :
- Core domain : Booking (use cases)
- Ports : BookingRepository, PaymentGateway, NotificationService
- Adapters : PostgreSQL adapter, Stripe adapter, Email adapter
Exercice 33 : Détection d'anti-patterns
class Service {
private static instance: Service;
public static getInstance() { /* Singleton */ }
public process(data: any): any {
this.validate(data);
this.transform(data);
this.saveToDatabase(data);
this.sendNotification(data);
this.logOperation(data);
return data;
}
}
Identifiez TOUS les anti-patterns présents et proposez une correction.
Exercice 34 : Architecture - Event-Driven Microservices
Concevez un système de traitement de commandes avec :
- Event sourcing pour les commandes
- CQRS pour la lecture
- Saga pour les transactions distribuées
- Outbox pattern pour la fiabilité
Exercice 35 : Refactoring - Legacy Code vers Clean Architecture
// code legacy
function handleRequest(req: any, res: any) {
const user = db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
const html = `<h1>${user.name}</h1><p>${user.email}</p>`;
fs.writeFileSync(`/tmp/report-${user.id}.html`, html);
email.send(user.email, 'Report', html);
res.send(html);
}
Refactorez en Clean Architecture / Hexagonale.
Exercice 36 : Meta-Programming - Générateur de patterns
Créez un générateur de code qui produit le boilerplate pour :
- Singleton
- Factory Method
- Observateur
- Stratégie À partir d'une configuration YAML/JSON.
Exercice 37 : Pattern Matching - Anti-pattern Detection Tool
Créez un analyseur statique qui détecte les anti-patterns dans du code :
- God Class (trop de méthodes)
- Long Method (> 20 lignes)
- Switch/ifelse abuse
- Duplicate code
Exercice 38 : Architecture - Design Patterns Decision Tree
Créez un arbre de décision qui guide le choix du pattern selon :
- Le problème (création, structure, comportement)
- Les contraintes (performance, flexibilité, couplage)
- Le contexte (monolithique, microservices, serverless)
Exercice 39 : Réécriture complète - Système de logging
class Logger {
static log(level: string, msg: string, format: string, destination: string) {
// 200+ lignes de code avec conditions et formats multiples
}
}
Réécrivez avec : Stratégie (formats), Décorateur (enrichissement), Chaîne de responsabilité (niveaux), Adaptateur (destinations).
Exercice 40 : Architecture - Design Patterns Cross-Cutting
Concevez l'architecture complète d'une application enterprise avec au moins 8 patterns différents, en justifiant chaque choix. L'application gère : auth, users, produits, commandes, paiements, notifications, rapports.