DEV Community

Software Design Principles: A Practical Example in Python

Introduction
Software design principles are essential guidelines that help developers create maintainable, scalable, and flexible software. Rather than simply writing code that works, good design ensures that software is easy to extend, understand, and test over time.

Some core principles include SOLID, DRY, KISS, and YAGNI. This article focuses on the SOLID principles, showing how they apply in a real-world Python example: designing a notification system that is extensible and testable.

The SOLID Principles Overview

  • Single Responsibility Principle (SRP): A class should have only one reason to change.
  • Open/Closed Principle (OCP): Software entities should be open for extension, but closed for modification.
  • Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types.
  • Interface Segregation Principle (ISP): Clients shouldn’t be forced to depend on interfaces they don’t use.
  • Dependency Inversion Principle (DIP): High-level modules shouldn’t depend on low-level modules; both should depend on abstractions.

Real-World Example: Notification System in Python
Imagine a system that sends notifications via multiple channels: Email and SMS. We want to design it so adding new channels (e.g., Push notifications, Webhooks) is easy without modifying existing code.

Step 1: Define an abstraction for notification

from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, message: str, recipient: str):
        pass
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement concrete notifiers

class EmailNotifier(Notifier):
    def send(self, message: str, recipient: str):
        print(f"Sending Email to {recipient}: {message}")

class SMSNotifier(Notifier):
    def send(self, message: str, recipient: str):
        print(f"Sending SMS to {recipient}: {message}")
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a notification service that uses dependency injection

class NotificationService:
    def __init__(self, notifier: Notifier):
        self.notifier = notifier

    def notify(self, message: str, recipient: str):
        self.notifier.send(message, recipient)
Enter fullscreen mode Exit fullscreen mode

Step 4: Usage example

if __name__ == "__main__":
    email_notifier = EmailNotifier()
    sms_notifier = SMSNotifier()

    service = NotificationService(email_notifier)
    service.notify("Welcome!", "user@example.com")

    service = NotificationService(sms_notifier)
    service.notify("Your code is 1234", "+1234567890")
Enter fullscreen mode Exit fullscreen mode

Explanation

  • Each notifier class has a single responsibility (SRP).
  • Adding new notification channels does not modify existing classes, but extends functionality (OCP).
  • NotificationService depends on the abstraction Notifier (DIP), promoting flexibility.
  • Different concrete implementations can be swapped without changing the service (LSP).

Top comments (0)