Ilyes Bouzayen
11 juil. 2026

Les Design Patterns les Plus Utilisés en 2026 (avec Code)

16 min de lecture

Les Design Patterns les Plus Utilisés en 2026 (avec Code)

Les design patterns ne sont pas de la théorie académique. Ce sont des solutions éprouvées à des problèmes qui reviennent sans cesse. Voici les 5 patterns que vous utilisez déjà (sans le savoir).

1. Repository Pattern

Problème : le code métier mélange la logique et l'accès aux données.

// ❌ Sans Repository
async function getUser(id: string) {
  const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
  return result.rows[0];
}

// Avec Prisma + Repository
interface UserRepository {
  findById(id: string): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
  save(user: User): Promise<User>;
}

class PrismaUserRepository implements UserRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: string): Promise<User | null> {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.prisma.user.findUnique({ where: { email } });
  }

  async save(user: User): Promise<User> {
    return this.prisma.user.upsert({
      where: { id: user.id },
      create: user,
      update: user
    });
  }
}

// Le métier n'importe pas Prisma
class UserService {
  constructor(private repo: UserRepository) {}

  async getProfile(id: string) {
    const user = await this.repo.findById(id);
    if (!user) throw new NotFoundError('User');
    return { name: user.name, email: user.email };
  }
}

2. Strategy Pattern

Problème : des comportements qui changent selon le contexte.

// Stratégies de paiement
interface PaymentStrategy {
  pay(amount: number): Promise<PaymentResult>;
}

class StripePayment implements PaymentStrategy {
  async pay(amount: number) {
    return stripe.charges.create({ amount: amount * 100, currency: 'eur' });
  }
}

class PayPalPayment implements PaymentStrategy {
  async pay(amount: number) {
    return paypal.payments.create({ amount, currency: 'EUR' });
  }
}

// Le contexte ne connaît pas l'implémentation
class CheckoutService {
  constructor(private payment: PaymentStrategy) {}

  async checkout(cart: Cart) {
    const total = cart.getTotal();
    const result = await this.payment.pay(total);
    if (!result.success) throw new PaymentError();
    await this.clearCart(cart.id);
    return result;
  }
}

// Utilisation
const checkout = new CheckoutService(new StripePayment());
await checkout.checkout(cart);

3. Observer Pattern

Problème : déclencher des actions secondaires sans coupler les composants.

type EventHandler<T = any> = (data: T) => void | Promise<void>;

class EventBus {
  private handlers = new Map<string, Set<EventHandler>>();

  on<T>(event: string, handler: EventHandler<T>) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);
  }

  async emit<T>(event: string, data: T) {
    const handlers = this.handlers.get(event) ?? new Set();
    await Promise.all([...handlers].map(h => h(data)));
  }
}

// Utilisation
const bus = new EventBus();

// Module Email
bus.on('order:created', async (order) => {
  await sendEmail(order.userEmail, 'Commande confirmée !');
});

// Module Analytics
bus.on('order:created', async (order) => {
  await analytics.track('purchase', { total: order.total });
});

// Le service Order ne sait pas que l'email existe
await bus.emit('order:created', order);

4. Factory Pattern

Problème : créer des objets complexes sans exposer la logique de construction.

interface Notification {
  send(to: string, message: string): Promise<void>;
}

class NotificationFactory {
  static create(type: 'email' | 'sms' | 'push'): Notification {
    switch (type) {
      case 'email':
        return new EmailNotification();
      case 'sms':
        return new SMSNotification();
      case 'push':
        return new PushNotification();
      default:
        throw new Error(`Type inconnu: ${type}`);
    }
  }
}

// Utilisation
const notifier = NotificationFactory.create(user.preferences.channel);
await notifier.send(user.id, 'Votre commande est prête !');

5. Middleware Pattern (Chain of Responsibility)

Problème : exécuter une séquence de traitements sur une requête.

type Middleware = (
  ctx: Context,
  next: () => Promise<void>
) => Promise<void>;

class App {
  private middlewares: Middleware[] = [];

  use(middleware: Middleware) {
    this.middlewares.push(middleware);
  }

  async handle(ctx: Context) {
    let index = 0;
    const next = async () => {
      if (index < this.middlewares.length) {
        const mw = this.middlewares[index++];
        await mw(ctx, next);
      }
    };
    await next();
  }
}

// Chaîne de middlewares
app.use(async (ctx, next) => {
  ctx.start = Date.now();
  await next();
  console.log(`${ctx.method} ${ctx.path} - ${Date.now() - ctx.start}ms`);
});

app.use(async (ctx, next) => {
  const token = ctx.headers.authorization?.split(' ')[1];
  ctx.user = token ? await verifyToken(token) : null;
  await next();
});

app.use(async (ctx, next) => {
  if (ctx.path.startsWith('/admin') && !ctx.user?.isAdmin) {
    ctx.status = 403;
    return;
  }
  await next();
});

Quel pattern pour quel problème ?

Problème Pattern
Accès aux données Repository
Comportements interchangeables Strategy
Événements découplés Observer
Création d'objets complexes Factory
Traitements en chaîne Middleware

Conclusion

Les patterns ne sont pas de la sur-architecture. Ce sont des conventions qui rendent le code lisible par n'importe quel développeur de l'équipe.