Adapter (Pattern Adaptateur)
Pattern de conception structurel permettant la collaboration entre interfaces incompatibles en convertissant l'interface d'une classe en celle attendue.
Mis à jour le 8 janvier 2026
Le pattern Adapter, aussi appelé Wrapper, est un design pattern structurel qui permet à des classes aux interfaces incompatibles de collaborer ensemble. Il agit comme un pont de traduction entre deux systèmes distincts, convertissant les appels d'une interface vers une autre sans modifier le code source des classes existantes. Ce pattern est particulièrement précieux lors de l'intégration de bibliothèques tierces ou de systèmes legacy dans des architectures modernes.
Fondements du Pattern
- Convertit l'interface d'une classe (Adaptee) en une autre interface (Target) attendue par le client
- Permet la réutilisation de code existant sans modification directe, respectant le principe Open/Closed
- Existe en deux variantes : Adapter d'objet (composition) et Adapter de classe (héritage multiple)
- Sépare clairement la logique métier de la logique de conversion d'interface
Avantages Clés
- Réutilisabilité : intègre des composants existants sans modification de leur code source
- Flexibilité : facilite le remplacement de dépendances externes et l'évolution du système
- Responsabilité unique : isole la logique de conversion dans une classe dédiée
- Compatibilité : permet l'utilisation de bibliothèques tierces avec des interfaces différentes
- Testabilité : simplifie les tests unitaires en permettant l'injection de mocks adaptés
Exemple Concret
Imaginons une application qui doit intégrer un service de paiement tiers avec une interface différente de celle attendue par notre système :
// Interface attendue par notre application
interface PaymentProcessor {
processPayment(amount: number, currency: string): Promise<PaymentResult>;
refund(transactionId: string): Promise<RefundResult>;
}
// Service tiers avec interface incompatible
class StripePaymentService {
charge(cents: number, currencyCode: string, metadata: object): StripeCharge {
// Logique Stripe
return { id: 'ch_123', status: 'succeeded', amount: cents };
}
createRefund(chargeId: string): StripeRefund {
return { id: 'ref_456', status: 'succeeded' };
}
}
// Adapter qui rend Stripe compatible avec notre interface
class StripePaymentAdapter implements PaymentProcessor {
constructor(private stripeService: StripePaymentService) {}
async processPayment(amount: number, currency: string): Promise<PaymentResult> {
// Conversion : euros -> centimes
const cents = Math.round(amount * 100);
const charge = this.stripeService.charge(cents, currency.toUpperCase(), {});
return {
success: charge.status === 'succeeded',
transactionId: charge.id,
amount: charge.amount / 100
};
}
async refund(transactionId: string): Promise<RefundResult> {
const refund = this.stripeService.createRefund(transactionId);
return {
success: refund.status === 'succeeded',
refundId: refund.id
};
}
}
// Utilisation transparente
class CheckoutService {
constructor(private paymentProcessor: PaymentProcessor) {}
async checkout(cart: Cart): Promise<void> {
// Le service ne sait pas qu'il utilise Stripe via un adapter
const result = await this.paymentProcessor.processPayment(
cart.total,
'EUR'
);
if (result.success) {
console.log('Paiement réussi:', result.transactionId);
}
}
}
// Configuration
const stripeService = new StripePaymentService();
const paymentAdapter = new StripePaymentAdapter(stripeService);
const checkout = new CheckoutService(paymentAdapter);Mise en Œuvre
- Identifier l'interface cible (Target) attendue par votre code client
- Analyser l'interface existante (Adaptee) qui nécessite une adaptation
- Créer la classe Adapter implémentant l'interface Target
- Injecter l'instance de l'Adaptee dans le constructeur de l'Adapter
- Implémenter les méthodes Target en délégant et traduisant vers l'Adaptee
- Gérer les conversions de types, formats et conventions de nommage nécessaires
- Tester l'adapter avec des cas limites pour garantir la robustesse de la traduction
Conseil Pro
Privilégiez l'Adapter d'objet (composition) plutôt que l'Adapter de classe (héritage). La composition offre plus de flexibilité, permet d'adapter plusieurs classes simultanément et respecte mieux le principe de composition over inheritance. Utilisez des interfaces explicites pour documenter clairement le contrat attendu.
Outils et Frameworks Associés
- Spring Framework : utilise massivement les adapters pour l'intégration de différentes technologies
- NestJS : architecture modulaire facilitant la création d'adapters pour services externes
- TypeORM / Prisma : adapters de bases de données fournissant une interface unifiée
- Axios / Fetch Wrappers : adapters pour standardiser les appels HTTP
- Port-Adapter Architecture (Hexagonal) : approche architecturale basée sur le concept d'adapters
Le pattern Adapter est un pilier de l'architecture logicielle moderne, particulièrement dans les contextes microservices et d'intégration continue. En isolant les dépendances externes derrière des interfaces cohérentes, il permet aux équipes de développement de maintenir un code métier stable tout en conservant la flexibilité nécessaire pour évoluer technologiquement. Cette approche réduit significativement les coûts de maintenance et facilite les migrations progressives vers de nouvelles solutions.
