Interview Questions, Answers and Tutorials

Design patterns (Singleton, Factory, Observer)

Design patterns (Singleton, Factory, Observer)

Design patterns are like recipes in a cookbook. They show us how to solve common problems in programming in a way that has worked well for others before us. Let’s dive into some popular design patterns and understand them with simple examples in Python.

1. Singleton Pattern

What is it?

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. Think of it like having only one TV remote in the house. Everyone uses the same remote to control the TV.

Code Example

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

# Test the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # True, both are the same instance




Practice Question

Question: Modify the Singleton class to include a method that increments a counter every time it is called. Create two instances and call the method from both. Verify if the counter is shared.

2. Factory Pattern

What is it?

The Factory pattern is used to create objects without specifying the exact class of object that will be created. It’s like having a factory that makes toys. You tell the factory what kind of toy you want, and it gives you that toy.

Code Example

class Toy:
    def play(self):
        pass

class Car(Toy):
    def play(self):
        print("Vroom! Vroom!")

class Doll(Toy):
    def play(self):
        print("Hi! I'm a doll!")

class ToyFactory:
    @staticmethod
    def create_toy(toy_type):
        if toy_type == 'car':
            return Car()
        elif toy_type == 'doll':
            return Doll()

# Test the Factory
car = ToyFactory.create_toy('car')
doll = ToyFactory.create_toy('doll')

car.play()  # Vroom! Vroom!
doll.play()  # Hi! I'm a doll!




Practice Question

Question: Add a new toy class called Robot to the factory. Update the ToyFactory class to create this new toy. Test the factory by creating and playing with a Robot.

3. Observer Pattern

What is it?

The Observer pattern is used to notify multiple objects about any changes to a particular object. Think of it like subscribing to a YouTube channel. Whenever the channel uploads a new video, all subscribers are notified.

Code Example

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

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

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

class Observer:
    def update(self, message):
        pass

class Subscriber(Observer):
    def update(self, message):
        print("Received message:", message)

# Test the Observer
subject = Subject()
subscriber1 = Subscriber()
subscriber2 = Subscriber()

subject.attach(subscriber1)
subject.attach(subscriber2)

subject.notify("New video uploaded!")  # Both subscribers receive the message




Practice Question

Question: Create a new class called PremiumSubscriber that also implements the Observer interface. Modify the Subject class to differentiate between regular and premium subscribers when notifying them.

4. Strategy Pattern

What is it?

The Strategy pattern allows selecting an algorithm at runtime. It lets the algorithm vary independently from the clients that use it. Think of it like choosing different routes on a map. You can decide which route to take based on traffic conditions.

Code Example

class Strategy:
    def execute(self, a, b):
        pass

class AddStrategy(Strategy):
    def execute(self, a, b):
        return a + b

class SubtractStrategy(Strategy):
    def execute(self, a, b):
        return a - b

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

    def execute_strategy(self, a, b):
        return self._strategy.execute(a, b)

# Test the Strategy
add = AddStrategy()
subtract = SubtractStrategy()

context = Context(add)
print(context.execute_strategy(5, 3))  # 8

context = Context(subtract)
print(context.execute_strategy(5, 3))  # 2




Practice Question

Question: Add a new strategy called MultiplyStrategy that multiplies two numbers. Update the Context class to use this new strategy and test it.

Solutions to Practice Questions

Singleton Counter

class Singleton:
    _instance = None
    counter = 0

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

    def increment_counter(self):
        Singleton.counter += 1
        return Singleton.counter

# Test the Singleton with Counter
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1.increment_counter())  # 1
print(singleton2.increment_counter())  # 2, counter is shared




Factory with Robot

class Toy:
    def play(self):
        pass

class Car(Toy):
    def play(self):
        print("Vroom! Vroom!")

class Doll(Toy):
    def play(self):
        print("Hi! I'm a doll!")

class Robot(Toy):
    def play(self):
        print("I am a robot!")

class ToyFactory:
    @staticmethod
    def create_toy(toy_type):
        if toy_type == 'car':
            return Car()
        elif toy_type == 'doll':
            return Doll()
        elif toy_type == 'robot':
            return Robot()

# Test the Factory with Robot
car = ToyFactory.create_toy('car')
doll = ToyFactory.create_toy('doll')
robot = ToyFactory.create_toy('robot')

car.play()  # Vroom! Vroom!
doll.play()  # Hi! I'm a doll!
robot.play()  # I am a robot!




PremiumSubscriber

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

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

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

class Observer:
    def update(self, message):
        pass

class Subscriber(Observer):
    def update(self, message):
        print("Received message:", message)

class PremiumSubscriber(Observer):
    def update(self, message):
        print("Premium received message:", message)

# Test the Observer with PremiumSubscriber
subject = Subject()
subscriber1 = Subscriber()
subscriber2 = Subscriber()
premium_subscriber = PremiumSubscriber()

subject.attach(subscriber1)
subject.attach(subscriber2)
subject.attach(premium_subscriber)

subject.notify("New video uploaded!")  # All subscribers receive the message




MultiplyStrategy

class Strategy:
    def execute(self, a, b):
        pass

class AddStrategy(Strategy):
    def execute(self, a, b):
        return a + b

class SubtractStrategy(Strategy):
    def execute(self, a, b):
        return a - b

class MultiplyStrategy(Strategy):
    def execute(self, a, b):
        return a * b

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

    def execute_strategy(self, a, b):
        return self._strategy.execute(a, b)

# Test the Strategy with Multiply
add = AddStrategy()
subtract = SubtractStrategy()
multiply = MultiplyStrategy()

context = Context(add)
print(context.execute_strategy(5, 3))  # 8

context = Context(subtract)
print(context.execute_strategy(5, 3))  # 2

context = Context(multiply)
print(context.execute_strategy(5, 3))  # 15




By understanding these patterns and practicing with these examples, you can become a better programmer and write more efficient and reusable code. Happy coding!