[Python OOP] 5. Design Patterns in OOP

06 Nov 2023 By Code Bricks

5. Design Patterns in OOP

Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints you can customize to solve a particular design problem in your code.

5.1 Understanding Design Patterns

Design patterns can speed up the development process by providing tested, proven development paradigms. Effective software design requires considering issues that may not become visible until later in the implementation. Reusing design patterns helps to prevent such subtle issues and it also improves code readability for coders and architects who are familiar with the patterns.

5.2 Singleton Pattern

The Singleton Pattern ensures a class has only one instance and provides a global point of access to it.

class Singleton:
    _instances = {}
    
    def __new__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super().__new__(cls)
            cls._instances[cls] = instance
        return cls._instances[cls]

# Usage
singleton1 = Singleton()
singleton2 = Singleton()
assert singleton1 is singleton2  # Both instances are the same.

5.3 Factory Pattern

The Factory Pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.

class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class AnimalFactory:
    @staticmethod
    def get_animal(animal_type):
        animals = dict(dog=Dog, cat=Cat)
        return animals[animal_type]()

# Usage
factory = AnimalFactory()
dog = factory.get_animal("dog")
print(dog.speak())  # Output: Woof!
cat = factory.get_animal("cat")
print(cat.speak())  # Output: Meow!

5.4 Observer Pattern

The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

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

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

    def notify_observers(self, message):
        for observer in self._observers:
            observer.notify(message)

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

    def notify(self, message):
        print(f"{self.name} received message: {message}")

# Usage
subject = Subject()
observer1 = Observer("Observer1")
observer2 = Observer("Observer2")
subject.register_observer(observer1)
subject.register_observer(observer2)
subject.notify_observers("Hello!")  # Both observers receive the message.