DEV Community

Ryosuke Yano
Ryosuke Yano

Posted on

Introduction of 5 Design Patterns: Explained by Python

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
subhmsft profile image
Subhasish G • Edited

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.

Collapse
 
ryosuke profile image
Ryosuke Yano

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.