Aller au contenu
  1. Exemples/

Les Interfaces en Programmation Orientée Objet - Principe SOLID et Exemple Pratique

·7 mins· loading · loading · · ·
Conception Back-End
Adrien D'acunto
Auteur
Adrien D’acunto
Sommaire

Les Interfaces en Programmation Orientée Objet : Principe SOLID et Exemple Pratique
#

Introduction
#

Dans cet article, nous allons explorer le principe Interface Segregation Principle (ISP) des principes SOLID à travers un exemple concret de système de paiement. Nous verrons comment les interfaces permettent de créer du code flexible, maintenable et respectueux des bonnes pratiques de conception.

Qu’est-ce qu’une Interface ?
#

Une interface définit un contrat que les classes doivent respecter. Elle spécifie quoi faire sans préciser comment le faire. C’est un concept fondamental de l’abstraction en POO.

Avantages des interfaces
#

  • Découplage : Réduction des dépendances entre les composants
  • Flexibilité : Facilite l’ajout de nouvelles implémentations
  • Testabilité : Permet l’injection de dépendances et les mocks
  • Polymorphisme : Une même interface, plusieurs implémentations

Étude de Cas : Système de Paiement
#

Imaginons un système e-commerce nécessitant plusieurs méthodes de paiement. Voici comment structurer le code avec des interfaces.

Architecture Globale - Problème Initial
#

classDiagram
    class Payment {
        <>
        +processPayment(amount: number) void
        +refund(amount: number) void
        +saveCard() number
        +authorize3DS() void
        +validateIBAN() void
        +sendSMS() void
    }
    
    Payment <|-- CreditCardPayment
    Payment <|-- PayPalPayment
    Payment <|-- CashPayment
    
    class CreditCardPayment {
        -cardNumber: string
        -cvv: string
        +processPayment(amount: number) void
        +refund(amount: number) void
        +saveCard() void
        +authorize3DS() void
        +validateIBAN() void
        +sendSMS() void
    }
    
    class PayPalPayment {
        -email: string
        +processPayment(amount: number) void
        +refund(amount: number) void
        +saveCard() void 
        +authorize3DS() void 
        +validateIBAN() void 
        +sendSMS() void 
    }
    
    class CashPayment {
        +processPayment(amount: number) void
        +refund(amount: number) void
        +saveCard() void 
        +authorize3DS() void 
        +validateIBAN() void 
        +sendSMS() void 
    }

Problème : Cette interface viole le principe ISP ! Toutes les méthodes de paiement n’ont pas besoin de toutes ces fonctionnalités.

  • PayPal n’a pas besoin de saveCard(), authorize3DS(), validateIBAN() ou sendSMS()
  • Le paiement cash n’a besoin que de processPayment() et refund()
  • Chaque classe est forcée d’implémenter des méthodes inutiles

Implémentation avec Interface Monolithique
#

1. Paiement par Carte de Crédit
#

class CreditCardPayment extends Payment {

private cardNumber: string;
private cvv: string;

processPayment(amount: number): void {
    console.log('Paiement CB de ' + amount + '€');
}

refund(amount: number): void {
    console.log('Remboursement de ' + amount + '€');
}

saveCard(): void {
    console.log('Carte enregistrée');
}

authorize3DS(): void {
    console.log('Autorisation 3D Secure');
}

validateIBAN(): void {
    // N'a pas de sens pour une carte bancaire !
    throw new Error('Les cartes n'ont pas d'IBAN');
}

sendSMS(): void {
    console.log('SMS envoyé');
}
}

2. Paiement PayPal
#

class PayPalPayment extends Payment {

private email: string;

processPayment(amount: number): void {
    console.log('Paiement PayPal de ' + amount + '€');
}

refund(amount: number): void {
    console.log('Remboursement PayPal de ' + amount + '€');
}

saveCard(): void {
    // PayPal ne gère pas les cartes !
    throw new Error('PayPal ne gère pas les cartes');
}

authorize3DS(): void {
    // PayPal n'utilise pas 3DS !
    throw new Error('PayPal n'utilise pas 3DS');
}

validateIBAN(): void {
    // PayPal n'utilise pas d'IBAN !
    throw new Error('PayPal n'utilise pas d'IBAN');
}

sendSMS(): void {
    // PayPal envoie des emails, pas des SMS !
    throw new Error('PayPal envoie des emails');
}
}

3. Paiement en Espèces
#

class CashPayment extends Payment {

processPayment(amount: number): void {
    console.log('Paiement cash de ' + amount + '€');
}

refund(amount: number): void {
    console.log('Remboursement cash de ' + amount + '€');
}

saveCard(): void {
    // Le cash n'a pas de carte !
    throw new Error('Le cash n'a pas de carte');
}

authorize3DS(): void {
    // Pas de 3DS pour le cash !
    throw new Error('Pas de 3DS pour le cash');
}

validateIBAN(): void {
    // Pas d'IBAN pour le cash !
    throw new Error('Pas d'IBAN pour le cash');
}

sendSMS(): void {
    // Pas de SMS pour le cash !
    throw new Error('Pas de SMS pour le cash');
}
}

Conséquences du Mauvais Design
#

Ce code présente plusieurs problèmes graves :

  1. Violations du LSP : Les sous-classes lancent des exceptions pour des méthodes héritées
  2. Code mort : Beaucoup de méthodes inutilisées qui polluent le code
  3. Maintenance difficile : Ajouter une nouvelle méthode impacte toutes les classes
  4. Tests complexes : Nécessité de tester des méthodes qui ne devraient pas exister
  5. Compréhension difficile : L’API est trompeuse sur ce que chaque classe peut vraiment faire

Problème : Cette interface viole le principe ISP ! Toutes les méthodes de paiement n’ont pas besoin de toutes ces fonctionnalités.

Solution : Ségrégation des Interfaces
#

// Interface de base - fonctionnalités communes
interface Payment {
  processPayment(amount: number): void;
  refund(amount: number): void;
}

// Interface pour la sauvegarde de carte
interface CardStorable {
  saveCard(cardDetails: void): void;
}

// Interface pour l'authentification 3D Secure
interface SecureAuthenticable {
  authenticate(): boolean;
}

// Interface pour validation IBAN
interface Refundable {
  refund(amount: number): void;
}

Implémentation des Classes Concrètes
#

1. Paiement par Carte de Crédit
#

class CreditCardPayment implements Payment, CardStorable, SecureAuthenticable {
  private cardNumber: string;
  private cvv: number;

  processPayment(amount: number): void {
    console.log(`Paiement de ${amount} € par carte bancaire`);
    // Logique de traitement Stripe/Adyen
  }

  refund(amount: number): void {
    console.log(`Remboursement de ${amount} € sur la carte`);
  }

  saveCard(cardDetails: void): void {
    console.log('Carte enregistrée de manière sécurisée');
  }

  authenticate(): void {
    console.log('Authentication 3D Secure en cours...');
  }

  validateIBAN(): void {
    throw new Error('IBAN non applicable pour les cartes bancaires');
  }

  sendSMS(): void {
    console.log('SMS envoyé pour la validation');
  }
}

2. Paiement PayPal
#

class PayPalPayment implements Payment {
  private email: string;

  processPayment(amount: number): void {
    console.log(`Paiement PayPal de ${amount} € via ${this.email}`);
  }

  refund(amount: number): void {
    console.log(`Remboursement PayPal de ${amount} €`);
  }

  authorize3DS(): void {
    throw new Error('PayPal ne gère pas la 3DS');
  }

  validateIBAN(): void {
    throw new Error('PayPal n\'utilise pas d\'IBAN');
  }

  send3DS(): void {
    throw new Error('PayPal envoie ses propres emails');
  }
}

3. Paiement en Espèces
#

class CashPayment implements Payment {
  processPayment(amount: number): void {
    console.log(`Paiement cash de ${amount} €`);
  }

  refund(amount: number): void {
    console.log(`Remboursement cash de ${amount} €`);
  }

  // Pas besoin des autres méthodes !
}

Le Processeur de Paiements
#

class PaymentProcessor {
  process(payment: Payment, amount: number): void {
    payment.processPayment(amount);
    
    // Logique conditionnelle basée sur les capacités
    if (this.isCardStorable(payment)) {
      payment.saveCard();
    }
    
    if (this.isSecureAuth(payment)) {
      payment.authenticate();
    }
  }

  private isCardStorable(payment: Payment): payment is CardStorable {
    return 'saveCard' in payment;
  }

  private isSecureAuth(payment: Payment): payment is SecureAuthenticable {
    return 'authenticate' in payment;
  }
}

Diagrammes UML
#

Diagramme de Classes Complet
#

classDiagram
    class Payment {
        <>
        +processPayment(amount: number) void
        +refund(amount: number) void
    }
    
    class CardStorable {
        <>
        +saveCard(cardDetails: void) void
    }
    
    class SecureAuthenticable {
        <>
        +authenticate() boolean
    }
    
    class Refundable {
        <>
        +refund(amount: number) void
    }
    
    class PaymentProcessor {
        +process(payment: Payment, amount: number) void
    }
    
    class CreditCardPayment {
        -cardNumber: string
        -cvv: number
        +processPayment(amount: number) void
        +refund(amount: number) void
        +saveCard() void
        +authenticate() void
        +validateIBAN() void
        +sendSMS() void
    }
    
    class PayPalPayment {
        -email: string
        +processPayment(amount: number) void
        +refund(amount: number) void
    }
    
    class CashPayment {
        +processPayment(amount: number) void
        +refund(amount: number) void
    }
    
    Payment <|-- CreditCardPayment
    Payment <|-- PayPalPayment
    Payment <|-- CashPayment
    
    CardStorable <|.. CreditCardPayment
    SecureAuthenticable <|.. CreditCardPayment
    Refundable <|.. CreditCardPayment
    Refundable <|.. PayPalPayment
    
    PaymentProcessor ..> Payment : uses

Diagramme de Séquence : Processus de Paiement
#

sequenceDiagram
    participant Client
    participant Processor as PaymentProcessor
    participant Payment as CreditCardPayment
    participant Gateway as PaymentGateway
    
    Client->>Processor: process(payment, 100€)
    Processor->>Payment: processPayment(100)
    Payment->>Gateway: authorize(100€)
    Gateway-->>Payment: success
    Payment->>Payment: authenticate()
    Payment-->>Processor: success
    Processor->>Payment: saveCard()
    Payment-->>Processor: saved
    Processor-->>Client: payment confirmed

Utilisation Pratique
#

// Initialisation
const processor = new PaymentProcessor();

// Paiement par carte
const cardPayment = new CreditCardPayment();
processor.process(cardPayment, 99.99);

// Paiement PayPal
const paypalPayment = new PayPalPayment();
processor.process(paypalPayment, 149.99);

// Paiement cash
const cashPayment = new CashPayment();
processor.process(cashPayment, 50.00);

Principes SOLID Appliqués
#

Interface Segregation Principle (ISP)
#

Chaque interface est spécialisée : CardStorable, SecureAuthenticable, Refundable. Les classes n’implémentent que ce dont elles ont besoin.

Dependency Inversion Principle (DIP)
#

Le PaymentProcessor dépend de l’abstraction Payment, pas des implémentations concrètes.

Open/Closed Principle (OCP)
#

Ajouter un nouveau moyen de paiement ne nécessite pas de modifier le code existant.

Comparaison : Avant/Après
#

Aspect Sans Interfaces Avec Interfaces
Couplage Fort Faible
Évolutivité Difficile Facile
Tests Complexes Simples (mocks)
Maintenance Risquée Sécurisée

Conclusion
#

Les interfaces sont essentielles pour créer des systèmes robustes et maintenables. En appliquant le principe ISP, nous obtenons :

  • Code ciblé : Chaque classe implémente uniquement ce qu’elle utilise
  • Flexibilité : Ajout facile de nouveaux moyens de paiement
  • Testabilité : Injection de mocks pour les tests unitaires
  • Architecture claire : Séparation des responsabilités

Articles connexes

Les patrons de conception
·7 mins· loading · loading
Conception
Comment les base de données fonctionnent - Guide Complet
··25 mins· loading · loading
Back-End
Fonctions Commerciales et SAP - Guide Complet
··9 mins· loading · loading
ERP SAP
Introduction à SAP
··8 mins· loading · loading
ERP SAP
Modules SAP - Logistique, Finances, RH et Technique
··19 mins· loading · loading
ERP SAP
Programmation Orientée Objet - Guide Complet
··6 mins· loading · loading
Back-End