Behavioral Design Patterns
π§ What Are Behavioral Design Patterns?
Behavioral patterns define how objects interact, delegate responsibilities, and communicate. They focus on the flow of logic between objects rather than their structure.
π Key Behavioral Patterns (with Real-World Analogies)
| Pattern | Problem It Solves | Real-World Analogy |
|---|---|---|
| Strategy | Choose algorithm at runtime | Google Maps route selection |
| Observer | Notify multiple objects when one changes | YouTube subscribers get notified |
| Command | Encapsulate requests as objects (undo/redo) | TV remote buttons |
| Template Method | Define algorithm skeleton, let subclasses override steps | Making tea vs coffee |
| State | Object behavior changes with internal state | Traffic light |
| Chain of Responsibility | Pass request through a chain until handled | Customer complaint escalation |
| Mediator | Centralize communication between objects | Air traffic control |
| Memento | Save and restore object state | Ctrl+Z in Word |
| Iterator | Traverse a collection without exposing structure | TV remote channel browsing |
| Visitor | Add new operations to objects without modifying them | Tax inspector applying rules to businesses |
✅ Real-World Backend Use Cases
Here’s how these patterns shine in fintech/backend systems:
Strategy → Switching between payment discount algorithms (UPI cashback, loyalty, coupon).
Observer → Notify user, merchant, and delivery app when order status changes.
Command → Place, cancel, and return orders with undo/redo support.
State → Track order lifecycle: Pending → Shipped → Delivered → Cancelled.
Chain of Responsibility → Payment approval: Support → Manager → Director.
Mediator → Chat system where users communicate via a central server.
Memento → Save shopping cart state for “Save for Later”.
Visitor → Apply different tax rules to different product types.
πΉ 1. Strategy Pattern
Problem (Before)
In an e-commerce app, you support multiple payment methods (UPI, Card, NetBanking).
Client code is full of if-else:
if (paymentType.equals("UPI")) {
// pay via UPI
} else if (paymentType.equals("CARD")) {
// pay via Card
} else if (paymentType.equals("NETBANKING")) {
// pay via NetBanking
}
❌ Issue: Hard to add new payment methods → need to modify code everywhere.
Solution (After → Strategy Pattern)
Define a common strategy interface
PaymentStrategy.Implement different payment strategies (
UPIPayment,CardPayment, etc.).At runtime, choose strategy dynamically.
public interface PaymentStrategy {
void pay(double amount);
}
class CardPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paid " + amount + " using Card");
}
}
// Concrete strategies
class UPIPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paid " + amount + " using UPI");
}
}
package org.example.design.behaviour.strategy;
public class PaymentContext {
PaymentStrategy paymentStrategy;
public void setStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void payBill(double amount)
{
paymentStrategy.pay(amount);
}
}
public class PaymentContext {
PaymentStrategy paymentStrategy;
public void setStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void payBill(double amount)
{
paymentStrategy.pay(amount);
}
}
package org.example.design.behaviour.strategy;
public class Test {
public static void main(String[] args) {
PaymentContext context=new PaymentContext();
context.setStrategy(new UPIPayment());
context.payBill(300.0);
context.setStrategy(new CardPayment());
context.payBill(1000);
}
}
public class Test {
public static void main(String[] args) {
PaymentContext context=new PaymentContext();
context.setStrategy(new UPIPayment());
context.payBill(300.0);
context.setStrategy(new CardPayment());
context.payBill(1000);
}
}
π Benefits of using Context:
-
Cleaner client code → client just says “payBill()”.
-
Centralized management → only Context knows how to call the strategy.
-
Easier to switch strategies dynamically (
setStrategy()at runtime). -
Extensible → if tomorrow you want to log payments, add discounts, etc., you do it in Context, not in all clients.
π Real-World Analogy: E-Commerce Discounts
Imagine a shopping cart that can apply different discount strategies:
Seasonal Sale → 20% off
Loyalty Discount → 15% off
Coupon Code → 25% off
VIP Member → 35% off
Instead of hardcoding if-else logic, you inject the strategy dynamically.
public interface DiscountStrategy {
double applyDiscount(double amount);
}
// Concrete Strategies
public class CouponDiscountStrategy implements DiscountStrategy {
public double applyDiscount(double amount) {
return amount * 0.80;
}
}
public class VIPMemberDiscountStrategy implements DiscountStrategy {
public double applyDiscount(double amount) {
return amount * 0.65;
}
}
// Context Class
public class ShoppingCart {
private DiscountStrategy discountStrategy;
public ShoppingCart(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double checkout(double price) {
return discountStrategy.applyDiscount(price);
}
}
Main -
System.out.println("Final price: " + cart.checkout(1000.00));
πΉ Observer Pattern
π Problem (Before)
Imagine you are building a stock trading app (like Zerodha, Groww).
-
When a stock price changes, multiple users want to get notified:
-
Some via Email
-
Some via SMS
-
Some via Push notification
-
Without any pattern, the Stock class directly calls each notifier:
public class Stock {
private double price;
public void setPrice(double price) {
this.price = price;
// ❌ tightly coupled
sendEmail(price);
sendSMS(price);
sendPush(price);
}
}
π Solution (After → Observer Pattern)
-
Make
Stocka Subject. -
Make
Notifiers (Email, SMS, Push) Observers. -
When stock price changes → Stock automatically notifies all observers.
/...
Comments
Post a Comment