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)

PatternProblem It SolvesReal-World Analogy
StrategyChoose algorithm at runtimeGoogle Maps route selection
ObserverNotify multiple objects when one changesYouTube subscribers get notified
CommandEncapsulate requests as objects (undo/redo)TV remote buttons
Template MethodDefine algorithm skeleton, let subclasses override stepsMaking tea vs coffee
StateObject behavior changes with internal stateTraffic light
Chain of ResponsibilityPass request through a chain until handledCustomer complaint escalation
MediatorCentralize communication between objectsAir traffic control
MementoSave and restore object stateCtrl+Z in Word
IteratorTraverse a collection without exposing structureTV remote channel browsing
VisitorAdd new operations to objects without modifying themTax 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 (UPIPaymentCardPayment, etc.).

  • At runtime, choose strategy dynamically.


package org.example.design.behaviour.strategy;

public interface PaymentStrategy {
void pay(double amount);
}


package org.example.design.behaviour.strategy;

class CardPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paid " + amount + " using Card");
}
}


package org.example.design.behaviour.strategy;

// 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);

}


}

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);
}
}


👉 Benefits of using Context:

  1. Cleaner client code → client just says “payBill()”.

  2. Centralized management → only Context knows how to call the strategy.

  3. Easier to switch strategies dynamically (setStrategy() at runtime).

  4. 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.



// Strategy Interface
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 -

ShoppingCart cart = new ShoppingCart(new CouponDiscountStrategy());
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:


package org.example.design.behaiour.observer;

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 Notifiers (Email, SMS, Push) Observers.

  • When stock price changes → Stock automatically notifies all observers.

/...

Comments

Popular posts from this blog

Two Sum II - Input Array Is Sorted

Comparable Vs. Comparator in Java

Increasing Triplet Subsequence