Bird
0
0
LLDsystem_design~7 mins

When to use which behavioral pattern in LLD - System Design Guide

Choose your learning style9 modes available
Problem Statement
Without clear guidance on when to use each behavioral design pattern, developers often misuse or overcomplicate their code. This leads to tangled logic, poor maintainability, and difficulty adapting to changing requirements.
Solution
Each behavioral pattern addresses a specific kind of communication or responsibility delegation between objects. By understanding the problem each pattern solves, developers can choose the right one to organize interactions cleanly and flexibly, improving code clarity and adaptability.
Architecture
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│   Client      │──────▶│ Behavioral    │──────▶│ Receiver/     │
│ (Caller)      │       │ Pattern       │       │ Handler       │
└───────────────┘       └───────────────┘       └───────────────┘

Patterns like Chain of Responsibility pass requests along a chain until handled.
Patterns like Observer notify multiple dependents about state changes.
Strategy encapsulates interchangeable algorithms used by the client.

This diagram shows how behavioral patterns mediate communication between clients and receivers or handlers, organizing responsibilities and interactions.

Trade-offs
✓ Pros
Improves code flexibility by decoupling sender and receiver objects.
Enhances maintainability by localizing behavior changes to specific pattern classes.
Facilitates reuse of common interaction logic across different parts of the system.
Supports easier testing by isolating behaviors.
✗ Cons
Adds complexity by introducing additional classes and interfaces.
May lead to over-engineering if used without clear need.
Can make the flow of control harder to follow for beginners.
Use when your system has complex object interactions that need clear responsibility delegation, such as event handling, request routing, or algorithm selection. Typically beneficial when you have multiple ways to handle a request or when behavior changes dynamically.
Avoid when interactions are simple and direct, or when adding patterns would unnecessarily complicate the design without clear benefits, such as in small, straightforward applications with minimal behavior variation.
Real World Examples
Netflix
Uses the Strategy pattern to switch between different video encoding algorithms dynamically based on device capabilities.
Amazon
Applies the Observer pattern to update multiple inventory and pricing systems when product data changes.
LinkedIn
Implements Chain of Responsibility to process user requests through a series of validation and authorization steps.
Code Example
The before code mixes payment methods inside one class with conditionals, making it hard to extend. The after code uses the Strategy pattern to encapsulate each payment method in its own class, allowing PaymentProcessor to delegate payment without knowing details. This improves flexibility and maintainability.
LLD
### Before: No pattern, direct conditional logic
class PaymentProcessor:
    def pay(self, method, amount):
        if method == 'credit':
            print(f"Processing credit card payment of {amount}")
        elif method == 'paypal':
            print(f"Processing PayPal payment of {amount}")
        else:
            print("Unknown payment method")

### After: Strategy pattern applied
from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing credit card payment of {amount}")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing PayPal payment of {amount}")

class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def pay(self, amount):
        self.strategy.pay(amount)

# Usage
processor = PaymentProcessor(CreditCardPayment())
processor.pay(100)
processor.strategy = PayPalPayment()
processor.pay(200)
OutputSuccess
Alternatives
Command
Encapsulates a request as an object, allowing parameterization and queuing, rather than focusing on object interaction flow.
Use when: Use when you need to queue, log, or undo operations rather than just delegate behavior.
Mediator
Centralizes complex communication between objects into a mediator object, instead of distributing responsibility among them.
Use when: Choose when many objects interact in complex ways and you want to reduce direct dependencies.
Summary
Behavioral patterns organize how objects communicate and delegate responsibilities.
Choosing the right pattern depends on the interaction complexity and flexibility needs.
Using these patterns improves code maintainability but adds some complexity.