COURS COMPLET SUR LES DESIGN PATTERNS EN PROGRAMMATION
Table des matières
- Introduction aux Design Patterns
- Patterns Créationnels (Creational Patterns)
- Patterns Structurels (Structural Patterns)
- Patterns Comportementaux (Behavioral Patterns)
- Cas d'usage et bonnes pratiques
1. INTRODUCTION AUX DESIGN PATTERNS
Les design patterns sont des solutions éprouvées et réutilisables pour résoudre des problèmes courants en conception logicielle. Ils ont été formalisés par le Gang of Four (GoF) en 1994 et classifiés en trois catégories principales.
Classification des 23 patterns GoF:
Créationnels (5): Singleton, Factory Method, Abstract Factory, Builder, Prototype Structurels (7): Adapter, Composite, Decorator, Facade, Flyweight, Bridge, Proxy Comportementaux (11): Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
2. PATTERNS CRÉATIONNELS
2.1 SINGLETON PATTERN
Objectif: Garantir qu'une classe n'a qu'une seule instance et fournir un point d'accès global.
// Singleton Pattern
class DatabaseConnection {
private static instance: DatabaseConnection;
private connectionString: string;
private constructor(connectionString: string) {
this.connectionString = connectionString;
console.log("Connexion à la base de données...");
}
public static getInstance(connectionString: string = "default"): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(connectionString);
}
return DatabaseConnection.instance;
}
public getConnection(): string {
return this.connectionString;
}
}
// Utilisation
const db1 = DatabaseConnection.getInstance("Server=localhost");
const db2 = DatabaseConnection.getInstance("Server=localhost");
console.log(db1 === db2); // true - même instanceCas d'usage: Gestion de base de données, logging, configuration, cache
2.2 FACTORY METHOD PATTERN
Objectif: Créer des objets sans spécifier leurs classes concrètes exactes.
// Factory Method Pattern
interface Animal {
makeSound(): void;
}
class Dog implements Animal {
makeSound(): void {
console.log("Woof!");
}
}
class Cat implements Animal {
makeSound(): void {
console.log("Meow!");
}
}
abstract class AnimalCreator {
abstract createAnimal(): Animal;
getAnimal(): Animal {
return this.createAnimal();
}
}
class DogCreator extends AnimalCreator {
createAnimal(): Animal {
return new Dog();
}
}
class CatCreator extends AnimalCreator {
createAnimal(): Animal {
return new Cat();
}
}
// Utilisation
const dogCreator = new DogCreator();
const dog = dogCreator.getAnimal();
dog.makeSound(); // Woof!
const catCreator = new CatCreator();
const cat = catCreator.getAnimal();
cat.makeSound(); // Meow!Cas d'usage: Systèmes plug-and-play, frameworks, interfaces polymorphes
2.3 ABSTRACT FACTORY PATTERN
Objectif: Créer des familles d'objets liés sans spécifier leurs classes concrètes.
// Abstract Factory Pattern
interface Button {
paint(): void;
}
interface Checkbox {
paint(): void;
}
class WindowsButton implements Button {
paint(): void {
console.log("Rendu d'un bouton Windows");
}
}
class MacButton implements Button {
paint(): void {
console.log("Rendu d'un bouton Mac");
}
}
class WindowsCheckbox implements Checkbox {
paint(): void {
console.log("Rendu d'une case à cocher Windows");
}
}
class MacCheckbox implements Checkbox {
paint(): void {
console.log("Rendu d'une case à cocher Mac");
}
}
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
class WindowsFactory implements GUIFactory {
createButton(): Button {
return new WindowsButton();
}
createCheckbox(): Checkbox {
return new WindowsCheckbox();
}
}
class MacFactory implements GUIFactory {
createButton(): Button {
return new MacButton();
}
createCheckbox(): Checkbox {
return new MacCheckbox();
}
}
class Application {
private factory: GUIFactory;
constructor(factory: GUIFactory) {
this.factory = factory;
}
render(): void {
const button = this.factory.createButton();
const checkbox = this.factory.createCheckbox();
button.paint();
checkbox.paint();
}
}
// Utilisation
const app = new Application(new WindowsFactory());
app.render();Cas d'usage: Systèmes multi-plateforme, thèmes UI, familles de produits
2.4 BUILDER PATTERN
Objectif: Construire des objets complexes étape par étape.
// Builder Pattern
class House {
foundation: string = "";
walls: string = "";
roof: string = "";
doors: number = 0;
windows: number = 0;
describe(): void {
console.log(`
Fondation: ${this.foundation}
Murs: ${this.walls}
Toit: ${this.roof}
Portes: ${this.doors}
Fenêtres: ${this.windows}
`);
}
}
class HouseBuilder {
private house: House = new House();
buildFoundation(type: string): HouseBuilder {
this.house.foundation = type;
return this;
}
buildWalls(material: string): HouseBuilder {
this.house.walls = material;
return this;
}
buildRoof(type: string): HouseBuilder {
this.house.roof = type;
return this;
}
addDoors(number: number): HouseBuilder {
this.house.doors = number;
return this;
}
addWindows(number: number): HouseBuilder {
this.house.windows = number;
return this;
}
build(): House {
return this.house;
}
}
// Utilisation
const house = new HouseBuilder()
.buildFoundation("Béton")
.buildWalls("Brique")
.buildRoof("Tuiles")
.addDoors(2)
.addWindows(8)
.build();
house.describe();Cas d'usage: Création d'objets complexes avec nombreux paramètres optionnels
2.5 PROTOTYPE PATTERN
Objectif: Créer des objets par clonage d'un prototype plutôt que de zéro.
// Prototype Pattern
interface Cloneable {
clone(): Cloneable;
}
class User implements Cloneable {
name: string;
email: string;
role: string;
constructor(name: string, email: string, role: string) {
this.name = name;
this.email = email;
this.role = role;
}
clone(): User {
return new User(this.name, this.email, this.role);
}
display(): void {
console.log(`Utilisateur: ${this.name}, Email: ${this.email}, Rôle: ${this.role}`);
}
}
// Utilisation
const originalUser = new User("Jean", "jean@example.com", "Admin");
const clonedUser = originalUser.clone();
clonedUser.name = "Marie";
originalUser.display(); // Jean
clonedUser.display(); // MarieCas d'usage: Configuration par défaut, copie d'objets complexes
3. PATTERNS STRUCTURELS
3.1 ADAPTER PATTERN
Objectif: Faire fonctionner ensemble des interfaces incompatibles.
// Adapter Pattern
interface INewMediaPlayer {
playAudio(type: string, fileName: string): void;
}
interface ILegacyMediaPlayer {
playMp3(fileName: string): void;
}
class LegacyMediaPlayer implements ILegacyMediaPlayer {
playMp3(fileName: string): void {
console.log(`Lecture du fichier MP3: ${fileName}`);
}
}
class MediaAdapter implements INewMediaPlayer {
private legacyPlayer: ILegacyMediaPlayer;
constructor(legacyPlayer: ILegacyMediaPlayer) {
this.legacyPlayer = legacyPlayer;
}
playAudio(type: string, fileName: string): void {
if (type === "mp3") {
this.legacyPlayer.playMp3(fileName);
} else {
console.log(`Format non supporté: ${type}`);
}
}
}
// Utilisation
const legacyPlayer = new LegacyMediaPlayer();
const adapter = new MediaAdapter(legacyPlayer);
adapter.playAudio("mp3", "chanson.mp3");Cas d'usage: Intégration de code hérité, adaptation d'API tierces
3.2 DECORATOR PATTERN
Objectif: Ajouter des responsabilités à un objet dynamiquement.
// Decorator Pattern
interface IComponent {
operation(): string;
}
class ConcreteComponent implements IComponent {
operation(): string {
return "Composant simple";
}
}
abstract class Decorator implements IComponent {
protected component: IComponent;
constructor(component: IComponent) {
this.component = component;
}
operation(): string {
return this.component.operation();
}
}
class ConcreteDecoratorA extends Decorator {
operation(): string {
return super.operation() + " + Décoration A";
}
}
class ConcreteDecoratorB extends Decorator {
operation(): string {
return super.operation() + " + Décoration B";
}
}
// Utilisation
let component: IComponent = new ConcreteComponent();
component = new ConcreteDecoratorA(component);
component = new ConcreteDecoratorB(component);
console.log(component.operation());
// Résultat: Composant simple + Décoration A + Décoration BCas d'usage: Ajout de fonctionnalités sans altérer l'original
3.3 FACADE PATTERN
Objectif: Fournir une interface simplifiée à un système complexe.
// Facade Pattern
class LightSystem {
turnOn(): void {
console.log("Lumières allumées");
}
turnOff(): void {
console.log("Lumières éteintes");
}
}
class SecuritySystem {
arm(): void {
console.log("Système de sécurité armé");
}
disarm(): void {
console.log("Système de sécurité désarmé");
}
}
class HeatingSystem {
on(): void {
console.log("Chauffage activé");
}
off(): void {
console.log("Chauffage désactivé");
}
}
class HomeAutomationFacade {
private lights: LightSystem;
private security: SecuritySystem;
private heating: HeatingSystem;
constructor() {
this.lights = new LightSystem();
this.security = new SecuritySystem();
this.heating = new HeatingSystem();
}
goodMorning(): void {
this.lights.turnOn();
this.security.disarm();
this.heating.on();
}
goodNight(): void {
this.lights.turnOff();
this.security.arm();
this.heating.off();
}
}
// Utilisation
const homeAutomation = new HomeAutomationFacade();
homeAutomation.goodMorning();
homeAutomation.goodNight();Cas d'usage: Simplification de systèmes complexes
3.4 BRIDGE PATTERN
Objectif: Découpler une abstraction de son implémentation.
// Bridge Pattern
interface IDrawer {
drawCircle(x: number, y: number, radius: number): void;
drawSquare(x: number, y: number, size: number): void;
}
class VectorDrawer implements IDrawer {
drawCircle(x: number, y: number, radius: number): void {
console.log(`Dessin d'un cercle en vecteur à (${x}, ${y}) rayon: ${radius}`);
}
drawSquare(x: number, y: number, size: number): void {
console.log(`Dessin d'un carré en vecteur à (${x}, ${y}) taille: ${size}`);
}
}
class RasterDrawer implements IDrawer {
drawCircle(x: number, y: number, radius: number): void {
console.log(`Dessin d'un cercle en raster à (${x}, ${y}) rayon: ${radius}`);
}
drawSquare(x: number, y: number, size: number): void {
console.log(`Dessin d'un carré en raster à (${x}, ${y}) taille: ${size}`);
}
}
abstract class Shape {
protected drawer: IDrawer;
constructor(drawer: IDrawer) {
this.drawer = drawer;
}
abstract draw(): void;
}
class Circle extends Shape {
private x: number;
private y: number;
private radius: number;
constructor(drawer: IDrawer, x: number, y: number, radius: number) {
super(drawer);
this.x = x;
this.y = y;
this.radius = radius;
}
draw(): void {
this.drawer.drawCircle(this.x, this.y, this.radius);
}
}
// Utilisation
const vectorDrawer = new VectorDrawer();
const circle = new Circle(vectorDrawer, 50, 50, 25);
circle.draw();Cas d'usage: Abstraction et implémentation indépendantes
3.5 COMPOSITE PATTERN
Objectif: Composer des objets en structures arborescentes.
// Composite Pattern
interface FileSystemComponent {
display(indent: string): void;
}
class File implements FileSystemComponent {
constructor(private name: string) {}
display(indent: string): void {
console.log(indent + "Fichier: " + this.name);
}
}
class Directory implements FileSystemComponent {
private components: FileSystemComponent[] = [];
constructor(private name: string) {}
add(component: FileSystemComponent): void {
this.components.push(component);
}
remove(component: FileSystemComponent): void {
this.components = this.components.filter(c => c !== component);
}
display(indent: string): void {
console.log(indent + "Dossier: " + this.name);
this.components.forEach(component => {
component.display(indent + " ");
});
}
}
// Utilisation
const root = new Directory("root");
const file1 = new File("file1.txt");
const file2 = new File("file2.txt");
const subDir = new Directory("subdir");
const file3 = new File("file3.txt");
root.add(file1);
root.add(file2);
root.add(subDir);
subDir.add(file3);
root.display("");Cas d'usage: Structures hiérarchiques, menus imbriqués, systèmes de fichiers
3.6 FLYWEIGHT PATTERN
Objectif: Partager efficacement les objets pour économiser la mémoire.
// Flyweight Pattern
class Character {
constructor(private char: string, private font: string) {}
display(row: number, column: number): void {
console.log(`Caractère '${this.char}' (${this.font}) à (${row}, ${column})`);
}
}
class CharacterFactory {
private cache: Map<string, Character> = new Map();
getCharacter(char: string, font: string): Character {
const key = char + font;
if (!this.cache.has(key)) {
this.cache.set(key, new Character(char, font));
}
return this.cache.get(key)!;
}
getCacheSize(): number {
return this.cache.size;
}
}
// Utilisation
const factory = new CharacterFactory();
const char1 = factory.getCharacter("A", "Arial");
const char2 = factory.getCharacter("A", "Arial");
const char3 = factory.getCharacter("B", "Arial");
console.log(char1 === char2); // true - même objet
console.log("Objets en cache:", factory.getCacheSize()); // 2Cas d'usage: Réduction de la consommation mémoire
3.7 PROXY PATTERN
Objectif: Contrôler l'accès à un autre objet.
// Proxy Pattern
interface IDatabase {
query(sql: string): string;
}
class RealDatabase implements IDatabase {
query(sql: string): string {
console.log("Exécution de la requête:", sql);
return "Résultat de la base de données";
}
}
class DatabaseProxy implements IDatabase {
private realDatabase: RealDatabase | null = null;
private accessLog: string[] = [];
query(sql: string): string {
this.accessLog.push(`Accès à: ${new Date().toISOString()} - ${sql}`);
// Lazy loading
if (!this.realDatabase) {
this.realDatabase = new RealDatabase();
}
return this.realDatabase.query(sql);
}
getAccessLog(): string[] {
return this.accessLog;
}
}
// Utilisation
const proxy = new DatabaseProxy();
proxy.query("SELECT * FROM users");
proxy.query("SELECT * FROM products");
console.log(proxy.getAccessLog());Cas d'usage: Lazy loading, contrôle d'accès, caching
4. PATTERNS COMPORTEMENTAUX
4.1 OBSERVER PATTERN
Objectif: Notifier plusieurs objets de changements d'état.
// Observer Pattern
interface IObserver {
update(data: any): void;
}
class Subject {
private observers: IObserver[] = [];
attach(observer: IObserver): void {
this.observers.push(observer);
}
detach(observer: IObserver): void {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data: any): void {
this.observers.forEach(observer => observer.update(data));
}
}
class ConcreteObserver implements IObserver {
constructor(private id: string) {}
update(data: any): void {
console.log(`Observer ${this.id} a reçu: ${data}`);
}
}
// Utilisation
const subject = new Subject();
const obs1 = new ConcreteObserver("Observateur 1");
const obs2 = new ConcreteObserver("Observateur 2");
subject.attach(obs1);
subject.attach(obs2);
subject.notify("Changement d'état");
// Observateur 1 a reçu: Changement d'état
// Observateur 2 a reçu: Changement d'étatCas d'usage: Systèmes d'événements, mise à jour d'affichage
4.2 STRATEGY PATTERN
Objectif: Encapsuler une famille d'algorithmes interchangeables.
// Strategy Pattern
interface PaymentStrategy {
pay(amount: number): boolean;
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): boolean {
console.log(`Paiement par carte bancaire: ${amount}€`);
return true;
}
}
class PayPalPayment implements PaymentStrategy {
pay(amount: number): boolean {
console.log(`Paiement par PayPal: ${amount}€`);
return true;
}
}
class CryptoPayment implements PaymentStrategy {
pay(amount: number): boolean {
console.log(`Paiement en crypto-monnaie: ${amount}€`);
return true;
}
}
class ShoppingCart {
private paymentStrategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}
setPaymentStrategy(strategy: PaymentStrategy): void {
this.paymentStrategy = strategy;
}
checkout(amount: number): void {
this.paymentStrategy.pay(amount);
}
}
// Utilisation
const cart = new ShoppingCart(new CreditCardPayment());
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50);Cas d'usage: Algorithmes interchangeables à runtime
4.3 STATE PATTERN
Objectif: Permettre à un objet de modifier son comportement selon son état.
// State Pattern
interface DocumentState {
publish(context: Document): void;
}
class DraftState implements DocumentState {
publish(context: Document): void {
console.log("Document passé du brouillon à la modération");
context.setState(new ModerationState());
}
}
class ModerationState implements DocumentState {
publish(context: Document): void {
console.log("Document passé de la modération à publié");
context.setState(new PublishedState());
}
}
class PublishedState implements DocumentState {
publish(context: Document): void {
console.log("Document déjà publié");
}
}
class Document {
private state: DocumentState = new DraftState();
setState(state: DocumentState): void {
this.state = state;
}
publish(): void {
this.state.publish(this);
}
}
// Utilisation
const doc = new Document();
doc.publish(); // Document passé du brouillon à la modération
doc.publish(); // Document passé de la modération à publié
doc.publish(); // Document déjà publiéCas d'usage: Gestion d'états complexes, machines à état
4.4 COMMAND PATTERN
Objectif: Encapsuler une requête comme objet.
// Command Pattern
interface ICommand {
execute(): void;
undo(): void;
}
class Light {
private isOn = false;
on(): void {
this.isOn = true;
console.log("Lumière allumée");
}
off(): void {
this.isOn = false;
console.log("Lumière éteinte");
}
}
class TurnOnCommand implements ICommand {
constructor(private light: Light) {}
execute(): void {
this.light.on();
}
undo(): void {
this.light.off();
}
}
class TurnOffCommand implements ICommand {
constructor(private light: Light) {}
execute(): void {
this.light.off();
}
undo(): void {
this.light.on();
}
}
class RemoteControl {
private commands: ICommand[] = [];
executeCommand(command: ICommand): void {
command.execute();
this.commands.push(command);
}
undoLastCommand(): void {
const command = this.commands.pop();
if (command) {
command.undo();
}
}
}
// Utilisation
const light = new Light();
const turnOn = new TurnOnCommand(light);
const turnOff = new TurnOffCommand(light);
const remote = new RemoteControl();
remote.executeCommand(turnOn);
remote.executeCommand(turnOff);
remote.undoLastCommand(); // Annule la dernière commandeCas d'usage: Undo/Redo, files d'attente de commandes
4.5 ITERATOR PATTERN
Objectif: Accéder aux éléments d'une collection séquentiellement.
// Iterator Pattern
interface IIterator<T> {
hasNext(): boolean;
next(): T;
}
interface IIterable<T> {
getIterator(): IIterator<T>;
}
class NumberCollection implements IIterable<number> {
private numbers: number[] = [];
add(num: number): void {
this.numbers.push(num);
}
getIterator(): IIterator<number> {
return new NumberIterator(this.numbers);
}
}
class NumberIterator implements IIterator<number> {
private index = 0;
constructor(private collection: number[]) {}
hasNext(): boolean {
return this.index < this.collection.length;
}
next(): number {
return this.collection[this.index++];
}
}
// Utilisation
const collection = new NumberCollection();
collection.add(1);
collection.add(2);
collection.add(3);
const iterator = collection.getIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}Cas d'usage: Parcours de collections
4.6 CHAIN OF RESPONSIBILITY PATTERN
Objectif: Passer une requête le long d'une chaîne de gestionnaires.
// Chain of Responsibility Pattern
interface RequestHandler {
setNext(handler: RequestHandler): RequestHandler;
handle(request: Request): void;
}
interface Request {
type: string;
priority: number;
}
abstract class AbstractHandler implements RequestHandler {
protected next: RequestHandler | null = null;
setNext(handler: RequestHandler): RequestHandler {
this.next = handler;
return handler;
}
handle(request: Request): void {
this.handleRequest(request);
if (this.next) {
this.next.handle(request);
}
}
protected abstract handleRequest(request: Request): void;
}
class LoggingHandler extends AbstractHandler {
protected handleRequest(request: Request): void {
console.log(`[LOG] Requête de type: ${request.type}, Priorité: ${request.priority}`);
}
}
class AuthenticationHandler extends AbstractHandler {
protected handleRequest(request: Request): void {
console.log(`[AUTH] Vérification des droits pour: ${request.type}`);
}
}
class ProcessingHandler extends AbstractHandler {
protected handleRequest(request: Request): void {
console.log(`[PROCESS] Traitement de: ${request.type}`);
}
}
// Utilisation
const logging = new LoggingHandler();
const auth = new AuthenticationHandler();
const process = new ProcessingHandler();
logging.setNext(auth).setNext(process);
const request: Request = { type: "POST", priority: 1 };
logging.handle(request);Cas d'usage: Middleware, pipelines de traitement
4.7 TEMPLATE METHOD PATTERN
Objectif: Définir le squelette d'un algorithme dans une classe de base.
// Template Method Pattern
abstract class DataProcessor {
process(data: string): void {
const parsed = this.parse(data);
const validated = this.validate(parsed);
this.save(validated);
}
protected abstract parse(data: string): any;
protected abstract validate(data: any): boolean;
protected save(data: any): void {
console.log("Données sauvegardées");
}
}
class JSONProcessor extends DataProcessor {
protected parse(data: string): any {
console.log("Parsing JSON");
return JSON.parse(data);
}
protected validate(data: any): boolean {
console.log("Validation JSON");
return data !== null;
}
}
class XMLProcessor extends DataProcessor {
protected parse(data: string): any {
console.log("Parsing XML");
// Simulation du parsing XML
return data;
}
protected validate(data: any): boolean {
console.log("Validation XML");
return true;
}
}
// Utilisation
const jsonProcessor = new JSONProcessor();
jsonProcessor.process('{"name": "John"}');
const xmlProcessor = new XMLProcessor();
xmlProcessor.process("<root></root>");Cas d'usage: Algorithmes avec étapes communes
4.8 MEDIATOR PATTERN
Objectif: Réduire les dépendances chaotiques entre objets.
// Mediator Pattern
interface IChatMediator {
sendMessage(message: string, sender: User): void;
addUser(user: User): void;
}
abstract class User {
protected name: string;
protected mediator: IChatMediator;
constructor(name: string, mediator: IChatMediator) {
this.name = name;
this.mediator = mediator;
}
send(message: string): void {
this.mediator.sendMessage(message, this);
}
abstract receive(message: string, sender: string): void;
}
class ChatUser extends User {
receive(message: string, sender: string): void {
console.log(`${this.name} a reçu: "${message}" de ${sender}`);
}
}
class ChatRoom implements IChatMediator {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
sendMessage(message: string, sender: User): void {
console.log(`Message envoyé par ${sender.constructor.name}`);
this.users.forEach(user => {
if (user !== sender) {
// @ts-ignore
user.receive(message, sender.name);
}
});
}
}
// Utilisation
const chatRoom = new ChatRoom();
const user1 = new ChatUser("Alice", chatRoom);
const user2 = new ChatUser("Bob", chatRoom);
chatRoom.addUser(user1);
chatRoom.addUser(user2);
user1.send("Bonjour!");Cas d'usage: Communication entre objets complexe
4.9 MEMENTO PATTERN
Objectif: Capturer et restaurer l'état d'un objet.
// Memento Pattern
class GameState {
constructor(
public score: number,
public level: number,
public position: { x: number; y: number }
) {}
}
class GameMemento {
private state: GameState;
constructor(state: GameState) {
this.state = new GameState(state.score, state.level, { ...state.position });
}
getState(): GameState {
return this.state;
}
}
class Game {
private state: GameState;
constructor() {
this.state = new GameState(0, 1, { x: 0, y: 0 });
}
play(): void {
this.state.score += 100;
this.state.level += 1;
console.log(`Score: ${this.state.score}, Niveau: ${this.state.level}`);
}
saveState(): GameMemento {
return new GameMemento(this.state);
}
restoreState(memento: GameMemento): void {
this.state = memento.getState();
console.log(`État restauré - Score: ${this.state.score}, Niveau: ${this.state.level}`);
}
}
class GameSaveManager {
private saves: Map<string, GameMemento> = new Map();
save(name: string, memento: GameMemento): void {
this.saves.set(name, memento);
}
load(name: string): GameMemento | undefined {
return this.saves.get(name);
}
}
// Utilisation
const game = new Game();
const manager = new GameSaveManager();
game.play();
manager.save("save1", game.saveState());
game.play();
manager.save("save2", game.saveState());
const restored = manager.load("save1");
if (restored) {
game.restoreState(restored);
}Cas d'usage: Sauvegardes de jeux, undo/redo
4.10 VISITOR PATTERN
Objectif: Ajouter des opérations à une structure d'objets.
// Visitor Pattern
interface Element {
accept(visitor: Visitor): void;
}
interface Visitor {
visitConcreteElementA(element: ConcreteElementA): void;
visitConcreteElementB(element: ConcreteElementB): void;
}
class ConcreteElementA implements Element {
operationA(): string {
return "Operation A";
}
accept(visitor: Visitor): void {
visitor.visitConcreteElementA(this);
}
}
class ConcreteElementB implements Element {
operationB(): string {
return "Operation B";
}
accept(visitor: Visitor): void {
visitor.visitConcreteElementB(this);
}
}
class ConcreteVisitor implements Visitor {
visitConcreteElementA(element: ConcreteElementA): void {
console.log(element.operationA());
}
visitConcreteElementB(element: ConcreteElementB): void {
console.log(element.operationB());
}
}
// Utilisation
const elements: Element[] = [new ConcreteElementA(), new ConcreteElementB()];
const visitor = new ConcreteVisitor();
elements.forEach(element => element.accept(visitor));Cas d'usage: Opérations sur structures hiérarchiques
4.11 INTERPRETER PATTERN
Objectif: Représenter une grammaire et interpréter des phrases.
// Interpreter Pattern
interface Expression {
interpret(): boolean;
}
class TerminalExpression implements Expression {
constructor(private data: string) {}
interpret(): boolean {
return this.data.length > 0;
}
}
class AndExpression implements Expression {
constructor(private expr1: Expression, private expr2: Expression) {}
interpret(): boolean {
return this.expr1.interpret() && this.expr2.interpret();
}
}
class OrExpression implements Expression {
constructor(private expr1: Expression, private expr2: Expression) {}
interpret(): boolean {
return this.expr1.interpret() || this.expr2.interpret();
}
}
// Utilisation
const expr1: Expression = new TerminalExpression("Hello");
const expr2: Expression = new TerminalExpression("World");
const andExpr: Expression = new AndExpression(expr1, expr2);
const orExpr: Expression = new OrExpression(expr1, expr2);
console.log("ET:", andExpr.interpret()); // true
console.log("OU:", orExpr.interpret()); // trueCas d'usage: Parsers, analyseurs de langage
5. CAS D'USAGE ET BONNES PRATIQUES
Quand utiliser les patterns?
- Ne pas sur-utiliser: Les patterns ajoutent de la complexité
- Adapter au contexte: Choisir le pattern qui résout le problème
- Tester le code: Les patterns favorisent la testabilité
Roadmap d'apprentissage
- Semaine 1: Strategy, Factory, Singleton
- Semaine 2: Adapter, Decorator, Facade
- Semaine 3: Observer, Command, Builder
- Semaine 4: Pratiquer en refactorisantun projet
Ressources
- Refactoring.guru
- Documentation TypeScript
- Livres GoF Design Patterns
CONCLUSION
Les design patterns sont des outils puissants pour concevoir des logiciels flexibles et maintenables. La maîtrise de ces 23 patterns vous permettra d'écrire du code plus professionnel et de communiquer efficacement avec d'autres développeurs.