top of page

S.O.L.I.D in Python

Updated: Mar 4


If you want an OOP-based Python project that can grow without collapsing under its own weight, then the SOLID principles are not optional — they are the discipline that keeps a system understandable, extendable, and testable over time.


These five principles were formulated by Robert C. Martin to make object-oriented systems resilient to change — which is exactly what every real production system faces.


1. S — Single Responsibility Principle (SRP)

A class should have only one reason to change.

In plain terms:

A class must do one job and do it well.


❌ Bad Design


class Report:    
    def generate(self):        
        print("Generating Report")    

    def save_to_file(self):        
        print("Saving to file")   
 
    def send_email(self):        
        print("Sending email")

This class has 3 responsibilities:

  • business logic

  • persistence

  • communication


Now:

  • File format changes → modify class

  • Email provider changes → modify class

  • Report logic changes → modify class


One class is now fragile.


✅ Good Design


class Report:    
    def generate(self):        
        print("Generating Report")


class ReportSaver:    
    def save(self, report):        
        print("Saving to file")


class EmailService:    
    def send(self, report):        
        print("Sending email")

Each class now has one job.


Now your:

  • mocks become easy

  • TDD becomes realistic

  • FDD becomes incremental


2. O — Open/Closed Principle (OCP)

Software should be open for extension but closed for modification.

Meaning:

You should add new behaviourwithout touching working code.


❌ Bad Design


class Discount:    
   def calculate(self, customer_type, amount):        
        if customer_type == "regular":            
            return amount * 0.1        
        elif customer_type == "vip":            
            return amount * 0.2

Adding a new type = modify logic = risk regression.


✅ Good Design


class DiscountStrategy:    
    def apply(self, amount):        
        pass

class RegularDiscount(DiscountStrategy):    
    def apply(self, amount):        
        return amount * 0.1

class VIPDiscount(DiscountStrategy):    
    def apply(self, amount):        
        return amount * 0.2

Now new feature:


class SuperVIPDiscount(DiscountStrategy):    
    def apply(self, amount):        
        return amount * 0.3

No existing code touched.

Perfect for Feature Driven Development.


3. L — Liskov Substitution Principle (LSP)

Subclasses must be replaceable for their base class.

❌ Violation


class Bird:    
    def fly(self):        
        pass

class Penguin(Bird):    
    def fly(self):        
        raise Exception("Cannot fly")

Now anywhere expecting Bird breaks.


✅ Fix


class Bird:    
    pass

class FlyingBird(Bird):    
    def fly(self):        
        pass

class Sparrow(FlyingBird):    
    pass

class Penguin(Bird):    
    pass

No broken assumptions.

Inheritance must honour behaviour.


4. I — Interface Segregation Principle (ISP)

Don’t force classes to depend on methods they don’t use.

❌ Bad


class Worker:    
    def work(self): 
        pass    

    def eat(self): 
        pass

Robot must now implement eat().


✅ Good


class Workable:    
    def work(self): 
        pass

class Eatable:    
    def eat(self): 
        pass

class Human(Workable, Eatable):    
    pass

class Robot(Workable):    
    pass

Lean interfaces make:

  • mocking easier

  • testing faster

  • coupling lower


5. D — Dependency Inversion Principle (DIP)

Depend on abstractions, not concrete classes.

❌ Bad


class MySQLDatabase:    
    def save(self):        
        pass

class UserService:    
    def __init__(self):        
        self.db = MySQLDatabase()

Now:

You cannot test without real DB.


✅ Good


class Database:    
    def save(self):        
        pass

class MySQLDatabase(Database):    
    def save(self):        
        pass

class UserService:    
    def __init__(self, db: Database):        
        self.db = db

Now test:


mock_db = Mock()
service = UserService(mock_db)

Mocks slide in naturally.


Why SOLID Matters in Python Projects

Applying SOLID enables:

✔ true unit testing

✔ clean mocking in TDD

✔ safe refactoring

✔ plug-in architecture

✔ parallel FDD feature delivery

✔ legacy system rescue


Without SOLID:

every new feature modifies old code every modification risks regression every regression slows delivery

With SOLID:

new behaviour is added existing behaviour remains stable

The Real Outcome

SOLID transforms your system from:

tightly coupled implementation

into:

behaviour-driven architecture

— where features can evolve one responsibility at a time.

Comments

Rated 0 out of 5 stars.
No ratings yet

Commenting on this post isn't available anymore. Contact the site owner for more info.

Subscribe to get exclusive updates

Thanks for subscribing!

CONTACT ME
avatar-formal-round.png

Follow

  • Medium
  • Facebook
  • Twitter
  • LinkedIn
  • Instagram
  • Youtube
  • Paypal.Me
  • linktree

© 2019 - 2025 Biyi Akinpelu. The LORD Is My Banner

bottom of page