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.