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
Stock
a Subject. -
Make
Notifier
s (Email, SMS, Push) Observers. -
When stock price changes → Stock automatically notifies all observers.
/...
Comments
Post a Comment