0
0
LLDsystem_design~7 mins

Adapter pattern in LLD - System Design Guide

Choose your learning style9 modes available
Problem Statement
When integrating two systems or components that have incompatible interfaces, direct communication fails. This causes errors or forces rewriting existing code, increasing time and cost.
Solution
The adapter pattern acts as a translator between incompatible interfaces. It wraps one component and exposes a compatible interface so the other component can use it without changes.
Architecture
┌─────────────┐      ┌───────────────┐      ┌─────────────┐
│ Client      │─────▶│ Adapter       │─────▶│ Adaptee     │
│ (expects    │      │ (translates   │      │ (incompatible│
│ Target API) │      │ interface)    │      │ interface)  │
└─────────────┘      └───────────────┘      └─────────────┘

This diagram shows the client calling the adapter, which translates calls to the adaptee's incompatible interface.

Trade-offs
✓ Pros
Allows reuse of existing incompatible components without modifying them.
Decouples client code from specific implementations, improving flexibility.
Supports multiple adapters for different incompatible interfaces.
✗ Cons
Adds an extra layer, which can slightly impact performance.
Increases system complexity by introducing additional classes.
Requires maintenance of adapter code as interfaces evolve.
Use when you need to integrate legacy or third-party components with incompatible interfaces into your system without changing their code.
Avoid when interfaces are already compatible or when you control both sides and can refactor them directly.
Real World Examples
Stripe
Uses adapters to integrate with multiple payment gateways that have different APIs, providing a unified interface to clients.
Amazon
Applies adapter pattern to connect various internal services with legacy systems having incompatible interfaces.
LinkedIn
Uses adapters to allow new features to interact with older data storage systems without rewriting them.
Code Example
The before code fails because Client expects a 'print' method, but OldPrinter has 'print_text'. The adapter wraps OldPrinter and exposes a 'print' method that calls 'print_text', making the interfaces compatible.
LLD
### Before Adapter (incompatible interface)
class OldPrinter:
    def print_text(self, text):
        print(f"Old Printer: {text}")

class Client:
    def __init__(self, printer):
        self.printer = printer

    def print(self, message):
        # Expects method named 'print'
        self.printer.print(message)

printer = OldPrinter()
client = Client(printer)
# This will fail because OldPrinter has no 'print' method
# client.print("Hello")


### After Adapter
class PrinterAdapter:
    def __init__(self, old_printer):
        self.old_printer = old_printer

    def print(self, message):
        self.old_printer.print_text(message)

printer = OldPrinter()
adapter = PrinterAdapter(printer)
client = Client(adapter)
client.print("Hello")  # Works correctly
OutputSuccess
Alternatives
Facade pattern
Facade provides a simplified interface to a complex subsystem, while Adapter changes an interface to be compatible.
Use when: Choose Facade when you want to simplify usage of a complex system rather than adapt incompatible interfaces.
Decorator pattern
Decorator adds responsibilities to objects dynamically without changing their interface, Adapter changes the interface itself.
Use when: Choose Decorator when you want to add behavior without altering the interface.
Summary
The adapter pattern solves interface incompatibility by wrapping components with a compatible interface.
It enables reuse of existing code without modification and decouples client and service implementations.
Adapters add flexibility but introduce extra layers and maintenance overhead.