Introduction
I am writing this article since I felt compelled to learn this topic in order to step up to a higher level position as a software engineer. In software engineering, design patterns play a crucial role in crafting robust, scalable, and maintainable applications. This article explores various design patterns and demonstrates their implementation in Python, a versatile and user-friendly programming language.
1. Singleton Pattern
This pattern ensures that a class has only one instance throughout the application's lifecycle. it provides a global access point to this instance, making it ideal for scenarios where a single shared resource is required.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Usage
singleton_obj1 = Singleton()
singleton_obj2 = Singleton()
assert singleton_obj1 is singleton_obj2 # Both objects point to the same instance
2. Factory Pattern
This pattern centralizes object creation by utilizing a factory method that instantiates different classes based on specific conditions. it promotes loose coupling and simplifies object creation.
class Shape:
def draw(self):
pass
class Circle(Shape):
def draw(self):
print("Drawing a circle")
class Square(Shape):
def draw(self):
print("Drawing a square")
class ShapeFactory:
@staticmethod
def create_shape(shape_type):
if shape_type == "circle":
return Circle()
elif shape_type == "square":
return Square()
# Usage
factory = ShapeFactory()
circle = factory.create_shape("circle")
square = factory.create_shape("square")
circle.draw() # Output: Drawing a circle
square.draw() # Output: Drawing a square
3. Observer Pattern
This pattern establishes a one-to-many relationship between objects, allowing multiple observers to be notified of changes in a subject. It promotes flexibility and reduces dependencies between objects.
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
class Observer:
def update(self, message):
print(f"Received message: {message}")
# Usage
subject = Subject()
observer1 = Observer()
observer2 = Observer()
subject.attach(observer1)
subject.attach(observer2)
subject.notify("Hello, Observers!") # Output: Received message: Hello, Observers!
4. Strategy Pattern
This pattern allows algorithms to be selected dynamically at runtime by encapsulating them in separate classes. This promotes code reusability and flexibility in choosing different strategies.
# Strategy Pattern in Python
# Context
class PaymentContext:
def __init__(self, payment_strategy):
self._payment_strategy = payment_strategy
def set_payment_strategy(self, payment_strategy):
self._payment_strategy = payment_strategy
def make_payment(self, amount):
self._payment_strategy.pay(amount)
# Strategy Interfaces
class PaymentStrategy:
def pay(self, amount):
pass
# Concrete Strategies
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid ${amount} with Credit Card")
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid ${amount} with PayPal")
class BankTransferPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid ${amount} with Bank Transfer")
# Usage
credit_card_payment = CreditCardPayment()
paypal_payment = PayPalPayment()
bank_transfer_payment = BankTransferPayment()
payment_context = PaymentContext(credit_card_payment)
payment_context.make_payment(100) # Output: Paid $100 with Credit Card
payment_context.set_payment_strategy(paypal_payment)
payment_context.make_payment(50) # Output: Paid $50 with PayPal
payment_context.set_payment_strategy(bank_transfer_payment)
payment_context.make_payment(200) # Output: Paid $200 with Bank Transfer
5. MVC(Model-View-Controller) Pattern
This pattern is a software architectural design that separates an application into three interconnected components - Model, View, and Controller. The Model represents the data and business logic, the View handles the user interface, and the controller manages user input and interactions. This separation enhances maintainability.
# Model
class UserModel:
def __init__(self, name):
self.name = name
# View
class UserView:
def show_user_details(self, user):
print(f"User Name: {user.name}")
# Controller
class UserController:
def __init__(self, model, view):
self.model = model
self.view = view
def update_user_name(self, name):
self.model.name = name
def show_user_details(self):
self.view.show_user_details(self.model)
# Usage
user_model = UserModel("John Doe")
user_view = UserView()
user_controller = UserController(user_model, user_view)
user_controller.show_user_details() # Output: User Name: John Doe
user_controller.update_user_name("Jane Smith")
user_controller.show_user_details() # Output: User Name: Jane Smith
Conclusion
Design patterns are valuable tools that every software engineer should be acquainted with. They empower us to construct flexible, modular, and scalable systems. Through our implementation in Python, we have demonstrated how these patterns can be effectively utilized in code. Gaining a comprehensive understanding and applying these design patterns will significantly improve the quality and efficiency of your software projects. I look forward to delving deeper into design patterns in the future.
Top comments (2)
Excellent article showcasing most commonly used design patterns. Ideally there's a lot more to it, but then each design pattern has very specific use-cases to which it fits into. But to start with, these 5 are a must-know for every Sr. Engineer building highly scalable, maintainable cloud-native Apps. The best thing is - you could translate this knowledge to any programming language including Java, .NET and so on.
Thank you for your great comment, Subhasish G!
Definitely, learning other design patterns and trying to translate them into other languages is my next challenge. I'll do my best to continue learning and improve my technical level.