In-Depth Comparison of Design Patterns

Design patterns are proven solutions to common problems that occur repeatedly in software design. They provide a shared vocabulary and standard practices that improve code readability, scalability, and maintainability. These patterns are not specific to any programming language but serve as guiding templates for solving architectural challenges in object-oriented systems. By studying and applying design patterns, developers can build robust software systems that are easier to understand, extend, and debug. This in-depth comparison explores the various categories of design patterns—creational, structural, and behavioral—highlighting their core principles, use cases, strengths, and trade-offs.

Strategy vs Template

Template Method and Strategy can appear similar because both deal with encapsulating algorithms and promoting flexibility, they are not the same — they differ in intent, structure, and how they achieve extensibility.

Key Differences: Template Method vs Strategy Pattern

AspectTemplate MethodStrategy
Pattern TypeBehavioral (uses inheritance)Behavioral (uses composition)
How it worksDefines a skeleton of an algorithm in a base class and lets subclasses override specific steps.Defines a family of interchangeable algorithms and uses composition to choose one at runtime.
FlexibilityStatic — behavior is fixed at compile-time via subclassing.Dynamic — behavior can be swapped at runtime via composition.
Main PrincipleInheritance-based algorithm variation.Delegation-based algorithm variation.
Use WhenYou want a fixed structure with customizable steps.You want to switch algorithms dynamically or plug in different behaviors.
ExampleParsing different file formats (open → extract → analyze → close) where extract varies.Sorting with different strategies (QuickSort, MergeSort, HeapSort) plugged in dynamically.

Template Method

class PaymentProcessor:
    def process(self):
        self.validate()
        self.pay()

    def validate(self):
        pass

    def pay(self):
        pass
Python

Extend using inheritance to define validate and pay.

Strategy Pattern

class PaymentStrategy:
    def pay(self, amount):
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid {amount} with credit card")

class UPIBasedPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid {amount} with UPI")

# Client
class Checkout:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

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

checkout = Checkout(CreditCardPayment())
checkout.execute_payment(100)

checkout.strategy = UPIBasedPayment()  # dynamic swap
checkout.execute_payment(200)
Python

PatternAnalogy
Template MethodRecipe with fixed steps, but chefs can tweak some parts.
StrategyPlug-and-play tools — pick the right tool at runtime.

Problem Online Notification System

Problem Statement:

Design a notification system that sends notifications via different channels such as:

  • Email
  • SMS
  • Push

Each notification follows the same general template workflow:

  • Prepare message
  • Send message via selected channel
  • Log result

The “send message” step differs depending on the channel — this is a good use case for the Strategy Pattern.

The overall algorithm (prepare → send → log) is always the same — that’s where we use the Template Method Pattern.

Requirements:

  • Use Template Method to define the notification workflow.
  • Use Strategy Pattern for the pluggable channel behavior
  • Support runtime selection of strategy (SMS, Email, Push).
  • Show output logs for visibility.

Solution

Class Design Plan:

  • NotificationSender → Abstract class using Template Method (send_notification())
  • NotificationStrategy → Interface for send(message)
    • Implementations: EmailStrategy, SMSStrategy, PushStrategy
  • Concrete class: ConcreteNotificationSender that accepts a NotificationStrategy at runtime
from abc import ABC, abstractmethod

# Strategy Interface
class NotificationStrategy(ABC):
    @abstractmethod
    def send(self, message):
        pass

# Concrete Strategies
class EmailStrategy(NotificationStrategy):
    def send(self, message):
        print(f"[Email] Sending email with message: {message}")

class SMSStrategy(NotificationStrategy):
    def send(self, message):
        print(f"[SMS] Sending SMS with message: {message}")

class PushStrategy(NotificationStrategy):
    def send(self, message):
        print(f"[Push] Sending push notification: {message}")

# Template Method Pattern
class NotificationSender(ABC):
    def send_notification(self, message):
        self.prepare_message(message)
        self.send_message(message)      # Delegated to strategy
        self.log_result(message)

    def prepare_message(self, message):
        print(f"[System] Preparing message: {message}")

    @abstractmethod
    def send_message(self, message):
        pass

    def log_result(self, message):
        print(f"[System] Notification sent: {message}")

# Concrete Template + Strategy Composition
class ConcreteNotificationSender(NotificationSender):
    def __init__(self, strategy: NotificationStrategy):
        self.strategy = strategy

    def send_message(self, message):
        self.strategy.send(message)

# --- Client Code ---
if __name__ == "__main__":
    email_sender = ConcreteNotificationSender(EmailStrategy())
    sms_sender = ConcreteNotificationSender(SMSStrategy())
    push_sender = ConcreteNotificationSender(PushStrategy())

    print("\n--- Email Notification ---")
    email_sender.send_notification("Your order has been shipped.")

    print("\n--- SMS Notification ---")
    sms_sender.send_notification("OTP is 123456.")

    print("\n--- Push Notification ---")
    push_sender.send_notification("You have a new message.")
Python

Output

--- Email Notification ---
[System] Preparing message: Your order has been shipped.
[Email] Sending email with message: Your order has been shipped.
[System] Notification sent: Your order has been shipped.

--- SMS Notification ---
[System] Preparing message: OTP is 123456.
[SMS] Sending SMS with message: OTP is 123456.
[System] Notification sent: OTP is 123456.

--- Push Notification ---
[System] Preparing message: You have a new message.
[Push] Sending push notification: You have a new message.
[System] Notification sent: You have a new message.
Python

Key Takeaways:

  • Template Method: Controls fixed workflow (prepare → send → log)
  • Strategy: Allows runtime plug-in of send behavior
  • This combo is powerful for building systems with common flow but pluggable logic

Observer vs State

Key Differences Between Observer and State Patterns

AspectObserver PatternState Pattern
IntentNotify multiple dependent objects when a subject’s state changesAllow an object to alter its behavior when its internal state changes
Who reacts?Multiple external observers react to changesThe context object itself changes its behavior by changing internal state
Pattern TypeBehavioral (communication between objects)Behavioral (changing behavior based on state)
ExampleNews app: many users get updates when news is publishedMedia player: behaves differently in “Playing”, “Paused”, “Stopped” states
ParticipantsSubject, ObserverContext, State
TriggerSubject changes, notifies all observersContext delegates behavior to current State object

Real-World Analogy

Observer

  • You subscribe to YouTube channels.
  • When the channel uploads a video (subject changes), you get notified.
  • You’re not changing the behavior of the channel, just reacting.

State

  • Game character can be “Running”, “Jumping”, “Sleeping”.
  • Based on state, the same action() method behaves differently.
  • Internal state of character decides behavior.

Observer Pattern (One triggers, others react)

class Subject:
    def __init__(self):
        self._observers = []
    def attach(self, obs):
        self._observers.append(obs)
    def notify(self, message):
        for obs in self._observers:
            obs.update(message)

class Observer:
    def update(self, msg):
        print("Got:", msg)

# Usage
subject = Subject()
subject.attach(Observer())
subject.notify("New data available")
Python

State Pattern (Same method behaves differently based on state)

class State:
    def handle(self):
        pass

class Playing(State):
    def handle(self):
        print("Playing music")

class Paused(State):
    def handle(self):
        print("Paused music")

class Player:
    def __init__(self):
        self.state = Playing()  # Initial state

    def change_state(self, state):
        self.state = state

    def action(self):
        self.state.handle()

# Usage
player = Player()
player.action()  # Playing music
player.change_state(Paused())
player.action()  # Paused music
Python

Summary

If you want to…Use
Notify others when something changesObserver Pattern
Change object’s behavior based on internal stateState Pattern

Resource

Leave a Comment