Skip to main content
  1. Examples/

Interfaces in Object-Oriented Programming - SOLID Principle and Practical Example

·6 mins· loading · loading · · ·
Design Back-End
Adrien D'acunto
Author
Adrien D’acunto
Table of Contents

Interfaces in Object-Oriented Programming: SOLID Principle and Practical Example
#

Introduction
#

In this article, we will explore the Interface Segregation Principle (ISP) of SOLID principles through a concrete example of a payment system. We will see how interfaces allow us to create flexible, maintainable code that respects good design practices.

What is an Interface?
#

An interface defines a contract that classes must respect. It specifies what to do without specifying how to do it. It is a fundamental concept of abstraction in OOP.

Advantages of interfaces
#

  • Decoupling: Reduction of dependencies between components
  • Flexibility: Facilitates the addition of new implementations
  • Testability: Enables dependency injection and mocks
  • Polymorphism: One interface, multiple implementations

Case Study: Payment System
#

Imagine an e-commerce system requiring multiple payment methods. Here is how to structure the code with interfaces.

Global Architecture - Initial Problem
#

classDiagram
    class Payment {
        <<abstract>>
        +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 
    }

Problem: This interface violates the ISP principle! Not all payment methods need all these features.

  • PayPal doesn’t need saveCard(), authorize3DS(), validateIBAN() or sendSMS()
  • Cash payment only needs processPayment() and refund()
  • Each class is forced to implement unnecessary methods

Implementation with Monolithic Interface
#

1. Credit Card Payment
#

class CreditCardPayment extends Payment {

private cardNumber: string;
private cvv: string;

processPayment(amount: number): void {
    console.log('Credit card payment of ' + amount + '€');
}

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

saveCard(): void {
    console.log('Card saved');
}

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

validateIBAN(): void {
    // Doesn't make sense for a credit card!
    throw new Error('Credit cards do not have IBAN');
}

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

2. PayPal Payment
#

class PayPalPayment extends Payment {

private email: string;

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

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

saveCard(): void {
    // PayPal does not manage cards!
    throw new Error('PayPal does not manage cards');
}

authorize3DS(): void {
    // PayPal doesn't use 3DS!
    throw new Error('PayPal does not use 3DS');
}

validateIBAN(): void {
    // PayPal doesn't use IBAN!
    throw new Error('PayPal does not use IBAN');
}

sendSMS(): void {
    // PayPal sends emails, not SMS!
    throw new Error('PayPal sends emails');
}
}

3. Cash Payment
#

class CashPayment extends Payment {

processPayment(amount: number): void {
    console.log('Cash payment of ' + amount + '€');
}

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

saveCard(): void {
    // Cash doesn't have a card!
    throw new Error('Cash does not have a card');
}

authorize3DS(): void {
    // No 3DS for cash!
    throw new Error('No 3DS for cash');
}

validateIBAN(): void {
    // No IBAN for cash!
    throw new Error('No IBAN for cash');
}

sendSMS(): void {
    // No SMS for cash!
    throw new Error('No SMS for cash');
}
}

Consequences of Poor Design
#

This code presents several serious problems:

  1. LSP violations: Subclasses throw exceptions for inherited methods
  2. Dead code: Many unused methods that clutter the code
  3. Difficult maintenance: Adding a new method impacts all classes
  4. Complex tests: Need to test methods that shouldn’t exist
  5. Difficult understanding: The API is misleading about what each class can actually do

Problem: This interface violates the ISP principle! Not all payment methods need all these features.

Solution: Interface Segregation
#

// Base interface - common features
interface Payment {
  processPayment(amount: number): void;
  refund(amount: number): void;
}

// Interface for card storage
interface CardStorable {
  saveCard(cardDetails: void): void;
}

// Interface for 3D Secure authentication
interface SecureAuthenticable {
  authenticate(): boolean;
}

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

Implementation of Concrete Classes
#

1. Credit Card Payment
#

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

  processPayment(amount: number): void {
    console.log(`Payment of ${amount} € by credit card`);
    // Stripe/Adyen processing logic
  }

  refund(amount: number): void {
    console.log(`Refund of ${amount} € to the card`);
  }

  saveCard(cardDetails: void): void {
    console.log('Card saved securely');
  }

  authenticate(): void {
    console.log('3D Secure authentication in progress...');
  }

  validateIBAN(): void {
    throw new Error('IBAN not applicable for credit cards');
  }

  sendSMS(): void {
    console.log('SMS sent for validation');
  }
}

2. PayPal Payment
#

class PayPalPayment implements Payment {
  private email: string;

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

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

  authorize3DS(): void {
    throw new Error('PayPal does not manage 3DS');
  }

  validateIBAN(): void {
    throw new Error('PayPal does not use IBAN');
  }

  send3DS(): void {
    throw new Error('PayPal sends its own emails');
  }
}

3. Cash Payment
#

class CashPayment implements Payment {
  processPayment(amount: number): void {
    console.log(`Cash payment of ${amount} €`);
  }

  refund(amount: number): void {
    console.log(`Cash refund of ${amount} €`);
  }

  // No need for other methods!
}

The Payment Processor
#

class PaymentProcessor {
  process(payment: Payment, amount: number): void {
    payment.processPayment(amount);
    
    // Conditional logic based on capabilities
    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;
  }
}

UML Diagrams
#

Complete Class Diagram
#

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

Sequence Diagram: Payment Process
#

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

Practical Usage
#

// Initialization
const processor = new PaymentProcessor();

// Credit card payment
const cardPayment = new CreditCardPayment();
processor.process(cardPayment, 99.99);

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

// Cash payment
const cashPayment = new CashPayment();
processor.process(cashPayment, 50.00);

SOLID Principles Applied
#

Interface Segregation Principle (ISP)
#

Each interface is specialized: CardStorable, SecureAuthenticable, Refundable. Classes only implement what they need.

Dependency Inversion Principle (DIP)
#

The PaymentProcessor depends on the Payment abstraction, not on concrete implementations.

Open/Closed Principle (OCP)
#

Adding a new payment method does not require modifying existing code.

Comparison: Before/After
#

Aspect Without Interfaces With Interfaces
Coupling Strong Weak
Evolvability Difficult Easy
Tests Complex Simple (mocks)
Maintenance Risky Secure

Conclusion
#

Interfaces are essential for creating robust and maintainable systems. By applying the ISP principle, we obtain:

  • Targeted code: Each class implements only what it uses
  • Flexibility: Easy addition of new payment methods
  • Testability: Injection of mocks for unit tests
  • Clear architecture: Separation of concerns

Related

How Databases Work - Complete Guide
··23 mins· loading · loading
Back-End
Designs Patterns
·6 mins· loading · loading
Conception
Business Functions and SAP - Complete Guide
··7 mins· loading · loading
ERP SAP
Introduction to SAP
··6 mins· loading · loading
ERP SAP
SAP Modules - Logistics, Finance, HR and Technical
··19 mins· loading · loading
ERP SAP
Object-Oriented Programming - Complete Guide
··6 mins· loading · loading
Back-End