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!