Back to Home 17 patterns + examples Pyodide ready

Python Design Patterns
Reusable Solutions for Common Problems

Master the classic design patterns in Python: creational, structural, and behavioral. Each pattern includes a practical example, step‑by‑step explanation, and runnable code by @PoeticGlenn.

🐍 Interactive Python Code Editor

Write, edit, and run Python code directly in your browser. No setup required β€” powered by Pyodide.

Ready
Examples: Hello World Singleton Factory Observer Strategy
Tip: Use the example snippets to get started, or write your own Python code. All code snippets on this page also have Copy and Run buttons.

What Are Design Patterns and Why Do They Matter?

Design patterns are proven, reusable solutions to common software design problems. They are not finished code but templates that can be adapted to many different situations. Think of them as blueprints for writing more maintainable, flexible, and understandable code.

In the early 1990s, the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) catalogued 23 classic patterns in their book Design Patterns: Elements of Reusable Object-Oriented Software. These patterns are divided into three categories:

  • Creational – object creation mechanisms (Singleton, Factory, Builder, Prototype, Abstract Factory, Factory Method)
  • Structural – composition of classes and objects (Adapter, Decorator, Proxy, Facade)
  • Behavioral – communication between objects (Observer, Strategy, Command, Template Method, Iterator, Chain of Responsibility, State)

In this tutorial, you'll learn 17 essential patterns with practical Python examples. Each pattern is explained with:

  • Why this pattern is used – the problem it solves
  • How it operates – the mechanics of the pattern
  • Its significance and role – how it contributes to better software
  • How it affects the output – what you see when you run the code

By the end, you'll be able to recognise when to apply each pattern and implement them confidently in your own projects.

πŸ—οΈ Creational Patterns

πŸ”’ Singleton Pattern

Ensures a class has only one instance and provides a global point of access to it.

# Singleton using metaclass
class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class DatabaseConnection(metaclass=SingletonMeta):
    def __init__(self):
        self.connected = False
        print("Creating database connection...")

    def connect(self):
        if not self.connected:
            self.connected = True
            print("Connected to database")
        return "Connection established"

db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)

# Singleton using decorator
def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class ConfigManager:
    def __init__(self):
        self.settings = {"debug": True, "version": "1.0"}

config1 = ConfigManager()
config2 = ConfigManager()
print(config1 is config2)
Why this code has been utilized? The Singleton pattern solves the problem of needing exactly one instance of a class. This is common for resources like database connections, configuration managers, or logging services where having multiple instances would cause conflicts or waste resources.
How it operates? The pattern uses a metaclass (SingletonMeta) that overrides __call__ to intercept instance creation. It stores a dictionary of instances keyed by class. When a class is instantiated, it checks if an instance already exists; if not, it creates one and stores it. The decorator version wraps the class with a function that does the same.
Significance in the program: It guarantees that all parts of the program share the same object, ensuring consistency and saving memory.
Role in the program: It acts as a global point of access to a single resource.
How it affects the output: The print(db1 is db2) outputs True, proving both variables reference the same instance. The constructor message "Creating database connection..." appears only once, demonstrating that the instance is created only once.
πŸ’‘ Use Singleton when you need exactly one instance of a class (e.g., configuration, logging, database connections).
singletonmetaclassglobal state

🏭 Factory Pattern

Creates objects without specifying the exact class. Provides a common interface for object creation.

from abc import ABC, abstractmethod

# Product interface
class Vehicle(ABC):
    @abstractmethod
    def drive(self):
        pass

    @abstractmethod
    def honk(self):
        pass

# Concrete products
class Car(Vehicle):
    def drive(self):
        return "Driving a car on the road"

    def honk(self):
        return "Beep beep!"

class Bike(Vehicle):
    def drive(self):
        return "Riding a bike on the trail"

    def honk(self):
        return "Ring ring!"

class Truck(Vehicle):
    def drive(self):
        return "Driving a truck on the highway"

    def honk(self):
        return "Honk honk!"

# Factory
class VehicleFactory:
    def create_vehicle(self, vehicle_type):
        if vehicle_type == "car":
            return Car()
        elif vehicle_type == "bike":
            return Bike()
        elif vehicle_type == "truck":
            return Truck()
        else:
            raise ValueError(f"Unknown vehicle type: {vehicle_type}")

factory = VehicleFactory()
car = factory.create_vehicle("car")
bike = factory.create_vehicle("bike")

print(car.drive())
print(car.honk())
print(bike.drive())
Why this code has been utilized? The Factory pattern is used when you have a family of related objects and you want to decouple the client code from the concrete classes. It centralises object creation, making the system easier to extend and maintain.
How it operates? The factory class (VehicleFactory) has a method that takes a parameter (e.g., "car") and returns the corresponding product object. The client calls this method without knowing the internal logic of object creation.
Significance in the program: It promotes loose coupling, adheres to the Open/Closed principle (open for extension, closed for modification), and allows easy addition of new vehicle types without changing client code.
Role in the program: The factory acts as a central creator, hiding the instantiation details.
How it affects the output: The output shows the specific behaviours of the created vehicles (car.drive(), car.honk(), bike.drive()), demonstrating that the correct objects are created based on the input.
factorycreational

πŸ”¨ Builder Pattern

Separates the construction of a complex object from its representation, allowing step-by-step construction.

class Computer:
    def __init__(self):
        self.cpu = None
        self.ram = None
        self.storage = None
        self.gpu = None
        self.os = None

    def __str__(self):
        return f"Computer(CPU={self.cpu}, RAM={self.ram}, Storage={self.storage}, GPU={self.gpu}, OS={self.os})"

class ComputerBuilder:
    def __init__(self):
        self.computer = Computer()

    def set_cpu(self, cpu):
        self.computer.cpu = cpu
        return self

    def set_ram(self, ram):
        self.computer.ram = ram
        return self

    def set_storage(self, storage):
        self.computer.storage = storage
        return self

    def set_gpu(self, gpu):
        self.computer.gpu = gpu
        return self

    def set_os(self, os):
        self.computer.os = os
        return self

    def build(self):
        return self.computer

# Director
class ComputerDirector:
    def build_gaming_computer(self):
        builder = ComputerBuilder()
        return (builder
            .set_cpu("Intel i9")
            .set_ram("32GB DDR5")
            .set_storage("2TB NVMe SSD")
            .set_gpu("RTX 4090")
            .set_os("Windows 11")
            .build())

    def build_office_computer(self):
        builder = ComputerBuilder()
        return (builder
            .set_cpu("Intel i5")
            .set_ram("16GB DDR4")
            .set_storage("512GB SSD")
            .set_os("Ubuntu")
            .build())

director = ComputerDirector()
gaming_pc = director.build_gaming_computer()
office_pc = director.build_office_computer()

print(gaming_pc)
print(office_pc)
Why this code has been utilized? The Builder pattern is ideal when an object has many optional parameters or a complex construction process. It separates the construction logic from the final representation.
How it operates? A builder class provides methods to set each attribute, returning self to enable method chaining. A director orchestrates the construction steps to produce specific configurations.
Significance in the program: It makes the code more readable and maintainable, avoiding telescoping constructors. It also allows different representations to be built using the same construction process.
Role in the program: The builder constructs the product step by step; the director defines the order of steps for each configuration.
How it affects the output: The output shows two different computer configurations (gaming and office) with appropriate attributes, demonstrating that the builder can produce different variants.
builderfluent interfacecreational

🧬 Prototype Pattern

Creates new objects by cloning existing objects, avoiding the cost of creating from scratch.

import copy
from abc import ABC, abstractmethod

class Prototype(ABC):
    @abstractmethod
    def clone(self):
        pass

class Document(Prototype):
    def __init__(self, title, content, author):
        self.title = title
        self.content = content
        self.author = author
        self.metadata = {"created": "2026-01-01", "version": 1}

    def clone(self):
        return copy.deepcopy(self)

    def __str__(self):
        return f"Document(title='{self.title}', content='{self.content[:30]}...', author='{self.author}')"

original = Document("Report 2026", "This is the full content of the report...", "Glenn")
clone1 = original.clone()
clone1.title = "Report 2026 - Copy"
clone1.metadata["version"] = 2

clone2 = original.clone()
clone2.title = "Report 2026 - Draft"

print(original)
print(clone1)
print(clone2)
print(clone1.metadata)
Why this code has been utilized? When creating an object is expensive (e.g., involves network calls or heavy computation), the Prototype pattern allows you to copy an existing object instead of starting from scratch.
How it operates? A prototype interface declares a clone() method. Concrete prototypes implement it using copy.deepcopy() to create a full copy. Clients can then clone the prototype and modify the copy as needed.
Significance in the program: It reduces the cost of creating new objects and allows dynamic configuration.
Role in the program: The original object serves as a template; clones are independent copies.
How it affects the output: The output shows three documents: the original and two clones with different titles and metadata versions. The clones are independent – modifying clone1 does not affect original or clone2.
πŸ’‘ Prototype is useful when object creation is expensive or when you need many similar objects with slight variations.
prototypeclonecreational
πŸ”— Structural Patterns

πŸ”Œ Adapter Pattern

Allows incompatible interfaces to work together by acting as a bridge between them.

# Target interface
class PaymentProcessor:
    def process_payment(self, amount, currency):
        return f"Processing {amount} {currency}"

# Incompatible third-party service
class StripeAPI:
    def create_charge(self, amount_in_cents, currency_code):
        return f"Stripe charge: {amount_in_cents} cents in {currency_code}"

# Adapter
class StripeAdapter(PaymentProcessor):
    def __init__(self, stripe_api):
        self.stripe_api = stripe_api

    def process_payment(self, amount, currency):
        amount_in_cents = int(amount * 100)
        return self.stripe_api.create_charge(amount_in_cents, currency)

class PayPalAPI:
    def send_payment(self, amount_usd):
        return f"PayPal payment: ${amount_usd}"

class PayPalAdapter(PaymentProcessor):
    def __init__(self, paypal_api):
        self.paypal_api = paypal_api

    def process_payment(self, amount, currency):
        if currency != "USD":
            amount = amount * 1.1
        return self.paypal_api.send_payment(amount)

# Usage
stripe = StripeAdapter(StripeAPI())
paypal = PayPalAdapter(PayPalAPI())

print(stripe.process_payment(25.50, "USD"))
print(paypal.process_payment(25.50, "EUR"))
Why this code has been utilized? The Adapter pattern is used when you want to use an existing class but its interface does not match the one you need. It allows integration of third-party libraries or legacy code.
How it operates? The adapter implements the target interface and wraps the adaptee. It translates the target method calls to the adaptee's methods, converting parameters and return values as necessary.
Significance in the program: It promotes code reuse and decouples the client from the specific implementation of the adaptee.
Role in the program: The adapter acts as a translator between the client and the adaptee.
How it affects the output: The output shows that both Stripe and PayPal payments can be processed through the same interface, even though their internal APIs are completely different. The adapter converts the parameters (e.g., amount to cents) accordingly.
adapterwrapperstructural

🎨 Decorator Pattern

Dynamically adds behaviour to objects without modifying their structure. (This is the same as Python's built-in decorators but implemented as a pattern.)

from abc import ABC, abstractmethod

# Component interface
class Coffee(ABC):
    @abstractmethod
    def cost(self):
        pass

    @abstractmethod
    def description(self):
        pass

# Concrete component
class SimpleCoffee(Coffee):
    def cost(self):
        return 2.0

    def description(self):
        return "Simple coffee"

# Decorator base class
class CoffeeDecorator(Coffee):
    def __init__(self, coffee):
        self._coffee = coffee

    def cost(self):
        return self._coffee.cost()

    def description(self):
        return self._coffee.description()

# Concrete decorators
class Milk(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.5

    def description(self):
        return self._coffee.description() + ", milk"

class Sugar(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.2

    def description(self):
        return self._coffee.description() + ", sugar"

class WhippedCream(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.7

    def description(self):
        return self._coffee.description() + ", whipped cream"

# Usage
coffee = SimpleCoffee()
coffee = Milk(coffee)
coffee = Sugar(coffee)
coffee = WhippedCream(coffee)

print(coffee.description())
print(f"Total cost: ${coffee.cost():.2f}")
Why this code has been utilized? The Decorator pattern is used to add responsibilities to objects dynamically without affecting other objects of the same class. It's a flexible alternative to subclassing.
How it operates? The decorator class wraps the component and forwards calls to it, but can add extra behaviour before or after the call. Multiple decorators can be stacked to combine behaviours.
Significance in the program: It allows behaviour to be added at runtime, and combinations of decorators can be created easily.
Role in the program: Each decorator adds a specific enhancement (milk, sugar, whipped cream) to the base coffee.
How it affects the output: The output shows the full description "Simple coffee, milk, sugar, whipped cream" and the total cost ($3.40), demonstrating that each decorator added its cost and description.
decoratorwrapperstructural

πŸ›‘οΈ Proxy Pattern

Provides a surrogate or placeholder for another object to control access, add logging, or implement lazy loading.

from abc import ABC, abstractmethod

class Image(ABC):
    @abstractmethod
    def display(self):
        pass

class RealImage(Image):
    def __init__(self, filename):
        self.filename = filename
        self._load_from_disk()

    def _load_from_disk(self):
        print(f"Loading image from disk: {self.filename}")

    def display(self):
        return f"Displaying image: {self.filename}"

class ImageProxy(Image):
    def __init__(self, filename):
        self.filename = filename
        self._real_image = None

    def display(self):
        if self._real_image is None:
            self._real_image = RealImage(self.filename)
        return self._real_image.display()

# Proxy with access control
class ProtectedImageProxy(Image):
    def __init__(self, filename, user_role):
        self.filename = filename
        self.user_role = user_role
        self._real_image = None

    def display(self):
        if self.user_role != "admin":
            return "Access denied: insufficient permissions"
        if self._real_image is None:
            self._real_image = RealImage(self.filename)
        return self._real_image.display()

# Usage
image1 = ImageProxy("photo.jpg")
image2 = ProtectedImageProxy("secret.jpg", "guest")
image3 = ProtectedImageProxy("secret.jpg", "admin")

print(image1.display())
print(image2.display())
print(image3.display())
Why this code has been utilized? The Proxy pattern is used to control access to an object, implement lazy initialization, or add logging/caching without changing the real object.
How it operates? The proxy implements the same interface as the real subject. It holds a reference to the real subject and intercepts method calls, performing additional actions (like loading, access control) before delegating.
Significance in the program: It adds a layer of indirection that can manage the lifecycle of the real object or enforce security.
Role in the program: ImageProxy provides lazy loading; ProtectedImageProxy adds access control.
How it affects the output: For image1, the image loads only when display() is called (lazy). For image2, access is denied for a guest; for image3, an admin can view the image. The output shows these different behaviours.
πŸ’‘ Proxy is useful for lazy loading, access control, logging, and caching.
proxylazy loadingaccess control

πŸ›οΈ Facade Pattern

Provides a simplified interface to a complex subsystem, making it easier to use.

# Complex subsystem
class CPU:
    def start(self):
        return "CPU started"

    def execute(self, instruction):
        return f"Executing: {instruction}"

class Memory:
    def load(self, address, data):
        return f"Loading {data} at {address}"

    def read(self, address):
        return f"Reading from {address}"

class HardDrive:
    def read(self, sector):
        return f"Reading sector {sector}"

    def write(self, sector, data):
        return f"Writing {data} to sector {sector}"

# Facade
class ComputerFacade:
    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.hard_drive = HardDrive()

    def start_computer(self):
        result = []
        result.append(self.cpu.start())
        result.append(self.memory.load("0x0000", "bootloader"))
        result.append(self.hard_drive.read("boot_sector"))
        result.append(self.cpu.execute("boot sequence"))
        result.append("Computer ready")
        return result

    def shutdown(self):
        return ["Saving state", "Shutting down", "Power off"]

# Usage
computer = ComputerFacade()
for step in computer.start_computer():
    print(step)
Why this code has been utilized? The Facade pattern is used to hide the complexity of a subsystem and provide a unified, high‑level interface for the client.
How it operates? The facade composes the subsystem components and exposes simple methods that internally coordinate the interactions between them.
Significance in the program: It reduces coupling between the client and the subsystem, and makes the subsystem easier to use and test.
Role in the program: The facade coordinates the boot sequence: starting CPU, loading memory, reading from disk, and executing.
How it affects the output: The output is a clear sequence of steps ("CPU started", "Loading bootloader at 0x0000", etc.) that abstracts away the low‑level details.
facadesimplificationstructural
πŸ”„ Behavioral Patterns

πŸ‘€ Observer Pattern

Defines a one-to-many dependency between objects so that when one changes state, all dependents are notified.

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, data):
        pass

class Subject:
    def __init__(self):
        self._observers = []
        self._state = None

    def attach(self, observer):
        self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def notify(self):
        for observer in self._observers:
            observer.update(self._state)

    def set_state(self, state):
        self._state = state
        self.notify()

# Concrete observers
class EmailNotifier(Observer):
    def update(self, data):
        return f"Email: State changed to {data}"

class SMSNotifier(Observer):
    def update(self, data):
        return f"SMS: State changed to {data}"

class Logger(Observer):
    def update(self, data):
        return f"Log: State changed to {data}"

# Usage
subject = Subject()
email = EmailNotifier()
sms = SMSNotifier()
logger = Logger()

subject.attach(email)
subject.attach(sms)
subject.attach(logger)

print("Setting state to 'active':")
subject.set_state("active")

print("\nSetting state to 'inactive':")
subject.set_state("inactive")
Why this code has been utilized? The Observer pattern is used when a change in one object requires notifying many others, but you want to avoid tight coupling.
How it operates? The subject maintains a list of observers. When its state changes, it iterates over the list and calls the update() method on each observer, passing the new state.
Significance in the program: It enables event-driven architectures, promotes loose coupling, and allows dynamic subscription.
Role in the program: The subject notifies all attached observers (email, SMS, logger) when its state changes.
How it affects the output: For each state change, you see three lines of output: one from each observer, demonstrating that all are notified and react accordingly.
observerpublish-subscribeevent-driven

🎯 Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime.

from abc import ABC, abstractmethod

# Strategy interface
class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data):
        pass

# Concrete strategies
class BubbleSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        n = len(arr)
        for i in range(n):
            for j in range(0, n - i - 1):
                if arr[j] > arr[j + 1]:
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]
        return arr

class QuickSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        if len(arr) <= 1:
            return arr
        pivot = arr[len(arr) // 2]
        left = [x for x in arr if x < pivot]
        middle = [x for x in arr if x == pivot]
        right = [x for x in arr if x > pivot]
        return self.sort(left) + middle + self.sort(right)

class MergeSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        if len(arr) <= 1:
            return arr
        mid = len(arr) // 2
        left = self.sort(arr[:mid])
        right = self.sort(arr[mid:])
        return self._merge(left, right)

    def _merge(self, left, right):
        result = []
        i, j = 0, 0
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                result.append(left[i])
                i += 1
            else:
                result.append(right[j])
                j += 1
        result.extend(left[i:])
        result.extend(right[j:])
        return result

# Context
class DataProcessor:
    def __init__(self, strategy):
        self._strategy = strategy

    def set_strategy(self, strategy):
        self._strategy = strategy

    def process(self, data):
        return self._strategy.sort(data)

# Usage
data = [64, 34, 25, 12, 22, 11, 90]

processor = DataProcessor(BubbleSort())
print(processor.process(data))

processor.set_strategy(QuickSort())
print(processor.process(data))

processor.set_strategy(MergeSort())
print(processor.process(data))
Why this code has been utilized? The Strategy pattern is used when you have multiple algorithms for a specific task and want to switch them at runtime without altering the client code.
How it operates? The context holds a reference to a strategy object. The client can change the strategy using a setter. When the context's method is called, it delegates the work to the current strategy.
Significance in the program: It promotes the Open/Closed principle and makes the system flexible.
Role in the program: The processor can use different sorting strategies without changing its core logic.
How it affects the output: The output shows the same list sorted by three different algorithms – each produces the same sorted result but uses a different approach internally.
strategyalgorithminterchangeable

πŸ“‹ Command Pattern

Encapsulates a request as an object, allowing parameterization of clients with different requests and supporting undo operations.

from abc import ABC, abstractmethod

# Command interface
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

    @abstractmethod
    def undo(self):
        pass

# Receiver
class Light:
    def __init__(self, location):
        self.location = location
        self.is_on = False

    def turn_on(self):
        self.is_on = True
        return f"{self.location} light is ON"

    def turn_off(self):
        self.is_on = False
        return f"{self.location} light is OFF"

# Concrete commands
class TurnOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        return self.light.turn_on()

    def undo(self):
        return self.light.turn_off()

class TurnOffCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        return self.light.turn_off()

    def undo(self):
        return self.light.turn_on()

# Invoker
class RemoteControl:
    def __init__(self):
        self._history = []

    def execute_command(self, command):
        result = command.execute()
        self._history.append(command)
        return result

    def undo_last(self):
        if self._history:
            command = self._history.pop()
            return command.undo()
        return "Nothing to undo"

# Usage
living_room = Light("Living Room")
kitchen = Light("Kitchen")

remote = RemoteControl()

print(remote.execute_command(TurnOnCommand(living_room)))
print(remote.execute_command(TurnOffCommand(kitchen)))
print(remote.execute_command(TurnOnCommand(kitchen)))

print(remote.undo_last())
print(remote.undo_last())
print(remote.undo_last())
Why this code has been utilized? The Command pattern is used to decouple the sender of a request from the receiver, and to support operations like undo/redo, queuing, and logging.
How it operates? Each command object encapsulates a request by binding a receiver and an action. The invoker executes commands, and can store a history for undo.
Significance in the program: It allows you to parameterize objects with actions, and to easily add new commands without changing existing code.
Role in the program: The remote control acts as the invoker; the light is the receiver; the commands encapsulate turning on/off.
How it affects the output: The output shows the state of the lights as commands are executed, and then the undo steps reverse the actions in reverse order.
commandundobehavioral

πŸ“ Template Method Pattern

Defines the skeleton of an algorithm in a method, deferring some steps to subclasses.

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    def process(self, data):
        if not self._validate(data):
            return "Validation failed"
        cleaned = self._clean(data)
        transformed = self._transform(cleaned)
        return self._output(transformed)

    @abstractmethod
    def _validate(self, data):
        pass

    @abstractmethod
    def _clean(self, data):
        pass

    @abstractmethod
    def _transform(self, data):
        pass

    @abstractmethod
    def _output(self, data):
        pass

class CSVProcessor(DataProcessor):
    def _validate(self, data):
        return isinstance(data, list) and len(data) > 0

    def _clean(self, data):
        return [str(x).strip() for x in data if x is not None]

    def _transform(self, data):
        return ",".join(data)

    def _output(self, data):
        return f"CSV Output: {data}"

class JSONProcessor(DataProcessor):
    def _validate(self, data):
        return isinstance(data, dict)

    def _clean(self, data):
        return {k: str(v).strip() for k, v in data.items()}

    def _transform(self, data):
        return {k: v.upper() for k, v in data.items()}

    def _output(self, data):
        return f"JSON Output: {data}"

# Usage
csv_data = ["Apple", " Banana ", None, " Cherry"]
json_data = {"name": " Glenn ", "city": " Manila ", "country": " Philippines"}

csv_processor = CSVProcessor()
json_processor = JSONProcessor()

print(csv_processor.process(csv_data))
print(json_processor.process(json_data))
Why this code has been utilized? The Template Method pattern is used when you have an algorithm with invariant steps but some steps can vary. It avoids code duplication by putting the common algorithm in a base class.
How it operates? The base class defines a template method that calls abstract methods for the variable steps. Subclasses override these methods to provide specific implementations.
Significance in the program: It ensures that the algorithm structure is preserved while allowing flexibility in the details.
Role in the program: The base class defines the data processing pipeline; subclasses implement validation, cleaning, transformation, and output for specific formats.
How it affects the output: The output shows that both CSV and JSON processors follow the same steps but produce different formatted outputs.
template methodalgorithm skeletonbehavioral

πŸ”„ Iterator Pattern

Provides a way to access elements of an aggregate object sequentially without exposing its underlying representation.

from abc import ABC, abstractmethod

class Iterator(ABC):
    @abstractmethod
    def has_next(self):
        pass

    @abstractmethod
    def next(self):
        pass

class Aggregate(ABC):
    @abstractmethod
    def create_iterator(self):
        pass

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"'{self.title}' by {self.author}"

class BookCollection(Aggregate):
    def __init__(self):
        self._books = []

    def add_book(self, book):
        self._books.append(book)

    def create_iterator(self):
        return BookIterator(self._books)

class BookIterator(Iterator):
    def __init__(self, books):
        self._books = books
        self._index = 0

    def has_next(self):
        return self._index < len(self._books)

    def next(self):
        if self.has_next():
            book = self._books[self._index]
            self._index += 1
            return book
        raise StopIteration

# Reverse iterator
class ReverseBookIterator(Iterator):
    def __init__(self, books):
        self._books = books
        self._index = len(books) - 1

    def has_next(self):
        return self._index >= 0

    def next(self):
        if self.has_next():
            book = self._books[self._index]
            self._index -= 1
            return book
        raise StopIteration

# Usage
collection = BookCollection()
collection.add_book(Book("1984", "George Orwell"))
collection.add_book(Book("Brave New World", "Aldous Huxley"))
collection.add_book(Book("Fahrenheit 451", "Ray Bradbury"))

print("Forward iteration:")
iterator = collection.create_iterator()
while iterator.has_next():
    print(iterator.next())

print("\nReverse iteration:")
reverse_iterator = ReverseBookIterator(collection._books)
while reverse_iterator.has_next():
    print(reverse_iterator.next())
Why this code has been utilized? The Iterator pattern is used to provide a standard way to traverse a collection without exposing its internal structure.
How it operates? The aggregate (collection) provides a method that returns an iterator object. The iterator has has_next() and next() methods to traverse the elements sequentially.
Significance in the program: It decouples traversal logic from the collection, allows multiple simultaneous traversals, and can support different traversal orders (forward, reverse).
Role in the program: The collection creates iterators; the iterators handle the traversal.
How it affects the output: The output shows the books printed in forward order and then in reverse order, demonstrating that the same collection can be traversed differently.
iteratoraggregatebehavioral

🏒 Factory Method Pattern

Defines an interface for creating an object, but lets subclasses decide which class to instantiate.

from abc import ABC, abstractmethod

# Product interface
class Transport(ABC):
    @abstractmethod
    def deliver(self):
        pass

# Concrete products
class Truck(Transport):
    def deliver(self):
        return "Delivering by truck (land delivery)"

class Ship(Transport):
    def deliver(self):
        return "Delivering by ship (sea delivery)"

class Plane(Transport):
    def deliver(self):
        return "Delivering by plane (air delivery)"

# Creator abstract class
class Logistics(ABC):
    @abstractmethod
    def create_transport(self) -> Transport:
        pass

    def plan_delivery(self):
        transport = self.create_transport()
        return f"Planning delivery: {transport.deliver()}"

# Concrete creators
class RoadLogistics(Logistics):
    def create_transport(self) -> Transport:
        return Truck()

class SeaLogistics(Logistics):
    def create_transport(self) -> Transport:
        return Ship()

class AirLogistics(Logistics):
    def create_transport(self) -> Transport:
        return Plane()

# Usage
road = RoadLogistics()
sea = SeaLogistics()
air = AirLogistics()

print(road.plan_delivery())
print(sea.plan_delivery())
print(air.plan_delivery())
Why this code has been utilized? The Factory Method pattern is used when a class cannot anticipate the type of objects it needs to create, and it wants to let subclasses decide.
How it operates? The creator class declares a factory method that returns a product. Subclasses override this method to return a specific product.
Significance in the program: It promotes loose coupling and follows the Open/Closed principle – you can add new product types by creating new subclasses without modifying existing code.
Role in the program: Each logistics class overrides create_transport() to return the appropriate transport.
How it affects the output: The output shows different delivery methods depending on the logistics subclass.
πŸ’‘ The Factory Method pattern is a creational pattern that allows subclasses to determine which object to create, promoting loose coupling.
factory methodcreationalpolymorphism

🏒 Abstract Factory Pattern

Provides an interface for creating families of related or dependent objects without specifying concrete classes.

from abc import ABC, abstractmethod

# Abstract products
class Chair(ABC):
    @abstractmethod
    def sit_on(self):
        pass

class Sofa(ABC):
    @abstractmethod
    def lie_on(self):
        pass

# Concrete products - Victorian
class VictorianChair(Chair):
    def sit_on(self):
        return "Sitting on a Victorian chair"

class VictorianSofa(Sofa):
    def lie_on(self):
        return "Lying on a Victorian sofa"

# Concrete products - Modern
class ModernChair(Chair):
    def sit_on(self):
        return "Sitting on a modern chair"

class ModernSofa(Sofa):
    def lie_on(self):
        return "Lying on a modern sofa"

# Concrete products - Art Deco
class ArtDecoChair(Chair):
    def sit_on(self):
        return "Sitting on an Art Deco chair"

class ArtDecoSofa(Sofa):
    def lie_on(self):
        return "Lying on an Art Deco sofa"

# Abstract Factory
class FurnitureFactory(ABC):
    @abstractmethod
    def create_chair(self) -> Chair:
        pass

    @abstractmethod
    def create_sofa(self) -> Sofa:
        pass

# Concrete factories
class VictorianFurnitureFactory(FurnitureFactory):
    def create_chair(self) -> Chair:
        return VictorianChair()

    def create_sofa(self) -> Sofa:
        return VictorianSofa()

class ModernFurnitureFactory(FurnitureFactory):
    def create_chair(self) -> Chair:
        return ModernChair()

    def create_sofa(self) -> Sofa:
        return ModernSofa()

class ArtDecoFurnitureFactory(FurnitureFactory):
    def create_chair(self) -> Chair:
        return ArtDecoChair()

    def create_sofa(self) -> Sofa:
        return ArtDecoSofa()

# Usage
def furnish_room(factory: FurnitureFactory):
    chair = factory.create_chair()
    sofa = factory.create_sofa()
    return [chair.sit_on(), sofa.lie_on()]

victorian = VictorianFurnitureFactory()
modern = ModernFurnitureFactory()
artdeco = ArtDecoFurnitureFactory()

print("Victorian:", furnish_room(victorian))
print("Modern:", furnish_room(modern))
print("Art Deco:", furnish_room(artdeco))
Why this code has been utilized? The Abstract Factory pattern is used when you need to create families of related objects that are designed to work together, and you want to ensure consistency among them.
How it operates? An abstract factory declares creation methods for each product type. Concrete factories implement these methods to return products from a specific family.
Significance in the program: It guarantees that products from the same family are used together, and makes it easy to swap entire families.
Role in the program: Each factory produces a consistent set of furniture (chair and sofa) in a particular style.
How it affects the output: The output shows that each factory returns products with the same style (e.g., "Victorian:" outputs Victorian chair and sofa descriptions).
abstract factoryfamilies of productscreational

πŸ”— Chain of Responsibility

Passes a request along a chain of handlers, where each handler decides to process it or pass it to the next.

from abc import ABC, abstractmethod

class Handler(ABC):
    def __init__(self):
        self._next = None

    def set_next(self, handler):
        self._next = handler
        return handler

    @abstractmethod
    def handle(self, request):
        if self._next:
            return self._next.handle(request)
        return "Request unhandled"

class AuthenticationHandler(Handler):
    def handle(self, request):
        if not request.get("token"):
            return "Authentication failed: no token"
        print("Authentication passed")
        return super().handle(request)

class AuthorizationHandler(Handler):
    def handle(self, request):
        if request.get("role") not in ["admin", "user"]:
            return "Authorization failed: invalid role"
        print("Authorization passed")
        return super().handle(request)

class ValidationHandler(Handler):
    def handle(self, request):
        if not request.get("data"):
            return "Validation failed: no data"
        print("Validation passed")
        return super().handle(request)

class ProcessingHandler(Handler):
    def handle(self, request):
        data = request.get("data")
        return f"Processing complete: {data.upper()}"

# Usage
chain = AuthenticationHandler()
chain.set_next(AuthorizationHandler()).set_next(ValidationHandler()).set_next(ProcessingHandler())

request1 = {"token": "abc123", "role": "admin", "data": "important"}
request2 = {"token": "abc123", "role": "guest", "data": "important"}
request3 = {"role": "admin", "data": "important"}

print(chain.handle(request1))
print(chain.handle(request2))
print(chain.handle(request3))
Why this code has been utilized? The Chain of Responsibility pattern is used to decouple the sender of a request from its receivers, allowing multiple objects to handle the request.
How it operates? Handlers are linked in a chain. Each handler decides whether to process the request or pass it to the next handler. The request travels until it is handled or reaches the end.
Significance in the program: It allows dynamic configuration of the chain and avoids coupling the sender to specific handlers.
Role in the program: The chain performs authentication, authorization, validation, and processing in order.
How it affects the output: The output shows that a valid request (request1) passes all checks and returns "Processing complete: IMPORTANT". request2 fails authorization; request3 fails authentication (missing token). Each failure returns an appropriate error message.
chain of responsibilitymiddlewarebehavioral

πŸ“¦ State Pattern

Allows an object to alter its behaviour when its internal state changes. The object will appear to change its class.

from abc import ABC, abstractmethod

class State(ABC):
    @abstractmethod
    def handle(self, context):
        pass

class LockedState(State):
    def handle(self, context):
        context.state = UnlockedState()
        return "Phone unlocked"

class UnlockedState(State):
    def handle(self, context):
        context.state = LockedState()
        return "Phone locked"

class Context:
    def __init__(self, state):
        self.state = state

    def request(self):
        return self.state.handle(self)

# More complex example
class TrafficLight:
    def __init__(self):
        self.state = RedLight()

    def change(self):
        return self.state.change(self)

class RedLight:
    def change(self, light):
        light.state = GreenLight()
        return "Red β†’ Green: Go!"

class GreenLight:
    def change(self, light):
        light.state = YellowLight()
        return "Green β†’ Yellow: Caution!"

class YellowLight:
    def change(self, light):
        light.state = RedLight()
        return "Yellow β†’ Red: Stop!"

# Usage
phone = Context(LockedState())
print(phone.request())
print(phone.request())

traffic = TrafficLight()
for _ in range(6):
    print(traffic.change())
Why this code has been utilized? The State pattern is used when an object's behaviour depends on its state, and it must change behaviour at runtime.
How it operates? The context holds a reference to a state object. It delegates behaviour to the current state. States can transition to other states by changing the context's state.
Significance in the program: It encapsulates state-specific behaviour and eliminates large conditional statements.
Role in the program: The phone toggles between locked and unlocked; the traffic light cycles through red, green, yellow.
How it affects the output: For the phone, each request() toggles the state, outputting "Phone unlocked" then "Phone locked". For the traffic light, it cycles through the six transitions, printing each step.
statefinite state machinebehavioral

These design patterns provide reusable solutions to common problems in Python. Practice each pattern with the runnable examples above.

πŸ’› You've Mastered Python Design Patterns

From Singleton to State, you've explored 17 essential design patterns that will make your code more flexible, maintainable, and professional. Each pattern addresses a specific problem and provides a proven blueprint for solving it.

Remember, design patterns are tools, not rules. Use them when they fit your context, and don't force them where simpler solutions exist. The best way to internalise them is to practice – try modifying the examples, combine patterns, and apply them to your own projects.

If you found this guide helpful, share it with your network and follow @PoeticGlenn for more Python insights.