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