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.
Table of Contents
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
Aspect | Template Method | Strategy |
---|---|---|
Pattern Type | Behavioral (uses inheritance) | Behavioral (uses composition) |
How it works | Defines 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. |
Flexibility | Static — behavior is fixed at compile-time via subclassing. | Dynamic — behavior can be swapped at runtime via composition. |
Main Principle | Inheritance-based algorithm variation. | Delegation-based algorithm variation. |
Use When | You want a fixed structure with customizable steps. | You want to switch algorithms dynamically or plug in different behaviors. |
Example | Parsing 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
PythonExtend 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)
PythonPattern | Analogy |
---|---|
Template Method | Recipe with fixed steps, but chefs can tweak some parts. |
Strategy | Plug-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:
- 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.")
PythonOutput
--- 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.
PythonKey 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
Aspect | Observer Pattern | State Pattern |
---|---|---|
Intent | Notify multiple dependent objects when a subject’s state changes | Allow an object to alter its behavior when its internal state changes |
Who reacts? | Multiple external observers react to changes | The context object itself changes its behavior by changing internal state |
Pattern Type | Behavioral (communication between objects) | Behavioral (changing behavior based on state) |
Example | News app: many users get updates when news is published | Media player: behaves differently in “Playing”, “Paused”, “Stopped” states |
Participants | Subject , Observer | Context , State |
Trigger | Subject changes, notifies all observers | Context 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")
PythonState 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
PythonSummary
If you want to… | Use |
---|---|
Notify others when something changes | Observer Pattern |
Change object’s behavior based on internal state | State Pattern |