OOP Design Patterns in Python

Object-Oriented Programming (OOP) is a fundamental concept in Python that allows developers to build scalable and reusable code. Design patterns are reusable solutions to common software design problems, helping us structure our code efficiently. Python supports various OOP design patterns, categorized into three main types:

  • Creational Patterns – Focus on object creation mechanisms.
  • Structural Patterns – Focus on class and object composition.
  • Behavioral Patterns – Focus on communication between objects.

Creational Design Patterns

1. Singleton Pattern

The Singleton pattern ensures that only one instance of a class is created and provides a global access point to that instance.

How it works: We override the __new__ method in the class to check if an instance already exists. If an instance exists, we return the existing instance instead of creating a new one.

</>
Copy
class Singleton:
    _instance = None  # Class attribute to store the instance

    def __new__(cls):
        if cls._instance is None:  # Check if an instance already exists
            cls._instance = super().__new__(cls)  # Create a new instance
        return cls._instance  # Return the single instance

# Creating objects
obj1 = Singleton()
obj2 = Singleton()

# Checking if both objects are the same
print(obj1 is obj2)  # Output: True

Output

True

Since obj1 and obj2 reference the same instance, they are the same object.


Structural Design Patterns

Adapter Pattern

The Adapter pattern allows two incompatible interfaces to work together by acting as a bridge between them.

How it works: We create an adapter class that translates requests from one interface to another, allowing two incompatible classes to communicate.

</>
Copy
# Class with an incompatible interface
class OldPrinter:
    def old_print(self):
        return "Old Printer Output"

# Adapter class that makes OldPrinter compatible with new requirements
class PrinterAdapter:
    def __init__(self, old_printer):
        self.old_printer = old_printer

    def print_text(self):
        return self.old_printer.old_print()

# Using the Adapter
old_printer = OldPrinter()
adapter = PrinterAdapter(old_printer)

print(adapter.print_text())  # Output: Old Printer Output

Output

Old Printer Output

The adapter allows the OldPrinter class to be used with a new interface, making it compatible with modern systems.


Behavioral Design Patterns

Observer Pattern

The Observer pattern establishes a subscription mechanism where objects (observers) get notified when another object (subject) changes.

How it works: The Subject maintains a list of observers and notifies them of any changes. Observers register themselves to receive updates.

</>
Copy
# Subject (Observable)
class NewsPublisher:
    def __init__(self):
        self.subscribers = []

    def subscribe(self, subscriber):
        self.subscribers.append(subscriber)

    def unsubscribe(self, subscriber):
        self.subscribers.remove(subscriber)

    def notify(self, news):
        for subscriber in self.subscribers:
            subscriber.update(news)

# Observer
class Subscriber:
    def __init__(self, name):
        self.name = name

    def update(self, news):
        print(f"{self.name} received news: {news}")

# Example usage
publisher = NewsPublisher()

subscriber1 = Subscriber("Arjun")
subscriber2 = Subscriber("Ram")

publisher.subscribe(subscriber1)
publisher.subscribe(subscriber2)

publisher.notify("New Design Patterns Article Published!")

Output

Arjun received news: New Design Patterns Article Published!
Ram received news: New Design Patterns Article Published!

Both Arjun and Ram receive the news because they subscribed to the NewsPublisher. The observer pattern allows multiple objects to stay updated without tight coupling.


Conclusion

Design patterns help us write clean, efficient, and scalable code in Python. By using the right design pattern, we can:

  • Reduce code duplication.
  • Improve code readability and maintainability.
  • Ensure flexibility in software architecture.

Understanding these patterns will help you write better Python applications and design software in a structured way.