TLDR
SOLID is an acronym for classic 5 object oriented design principles, that are geared towards making software more maintainable and scalable. Reocurring theme in most of the principles is programming to the interface, not implementation.
Principle | Super-Quick Explanation |
Single Responsibility | 1 class should do 1 thing |
Open-Closed | don’t modify classes, implement interfaces instead |
Liskov Substitution | passing both parent and derived class does not change correctness of the program |
Interface Segregation | create minimal interfaces, so classes can implement only what makes sense for them |
Dependency Inversion | don’t make your class dependent on concretions, make it dependent on abstraction |
Rock-SOLID Code
SOLID principles were originally introduced by Robert C. Martin (Uncle Bob).
Software is in a constant state of flux (a.k.a. continuous movement, frequently changing). New use cases are added and old ones change faster than the release cycle. There is certainly no silver bullet to combat this endless struggle, but SOLID principles can get you far. The goal of this guide is to help you understand these principle as fast as possible. It is structured in a way that every principle first has it’s pseudo-official definition followed by my simplified take. Each principle is then concluded by an example that violates it and an example that employs given principle. Code with explanation is always collapsed under a corresponding section so don’t forget to click ▶️ to expand.
🐍 Side Note: In Python there are nointerface
andimplements
keywords. Instead interfaces are implemented using abstract classes. In the article, I refer to those occurrences as “interfaces” (quotation marks included).
Single-responsibility principle (SRP)
Every class should have only 1 responsibility
Create classes that do 1 thing only
❌ violation of SRP example
class Customer: def __init__(self, name: str, email: str): self.name = name self.email = email def create_customer(self): """ This method is responsible for creating the customer and saving it to the database. """ # code to create customer and save to database def send_email(self, message: str): """ This method is responsible for sending an email to the customer. """ # code to send email to customer
The principle is violated because
Customer
class has 2 responsibilities, creating customers and sending emails.✅ employment of SRP example
class Customer: def __init__(self, name: str, email: str): self.name = name self.email = email class CustomerCreator: def create_customer(self, name: str, email: str): """ This method is responsible for creating the customer and saving it to the database. """ # code to create customer and save to database class EmailSender: def send_email(self, customer: Customer, message: str): """ This method is responsible for sending an email to the customer. """ # code to send email to customer
We have 1 class for storing customer’s data
Customer
, 1 class to create customer CustomerCreator
, 1 class to send emails EmailSende
. Each class does only 1 thing only.Open-closed principle (OCP)
Entities should be open for extension, but closed for modification.
Use interfaces and abstract methods to create new classes (extend), don’t modify existing classes
❌ violation of OCP example
class Shape: def __init__(self, shape_type): self.shape_type = shape_type def area(self): if self.shape_type == 'circle': # code to calculate area of circle elif self.shape_type == 'square': # code to calculate area of square elif self.shape_type == 'rectangle': # code to calculate area of rectangle
if we want to add a new shape, say a triangle, we would have to modify the existing
Shape
class → class is not closed for modification and not designed to be extensible✅ employment of OCP example
class Shape: def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14 * (self.radius ** 2) class Square(Shape): def __init__(self, side): self.side = side def area(self): return self.side ** 2 class Rectangle(Shape): def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width
Shape
is an abstract base class with method area()
that is not implemented. Circle
, Square
, and Rectangle
classes inherit from Shape
and implement their own a
rea()
method→
Shape
class is closed for modification, new shapes can be created by inheriting from itLiskov segregation principle (LSP)
Instances of a superclass should be able to be replaced with objects of a subclass.
You can use both object constructed using superclass and subclass and the program will be still correct (the behavior of subclasses is consistent with the superclass)
❌ violation of LSP example
class Bird: def fly(self): print("Flying...") class Penguin(Bird): def fly(self): raise Exception("Penguins can't fly!")
We cannot interchange
Penguin
class with Bird
class, the behavior is overridden in an inconsistent way (printing vs. raising Exception)✅ employment of LSP example
class Vehicle: def start_engine(self): pass class Car(Vehicle): def start_engine(self): print("Starting car engine...") class Motorcycle(Vehicle): def start_engine(self): print("Starting motorcycle engine...") class Driver: def __init__(self, vehicle): self.vehicle = vehicle def drive(self): self.vehicle.start_engine() car = Car() motorcycle = Motorcycle() driver1 = Driver(car) driver1.drive() # Output: Starting car engine... driver2 = Driver(motorcycle) driver2.drive() # Output: Starting motorcycle engine...
both
Car
and Motorcycle
classes implement start_engine
method that is based on Vehicle
superclass and the behaviour is consistent, Driver
expects vehicle regardless of the actual type, both Vehicle
class and two of it’s subclasses can be passed with no problem Interface Substitution principle (ISP)
Classes should not have to implement methods they don't need
strive to create as small interfaces as possible so that class that needs methods outlined in an interface does not need to implement extra methods that does not make sense for it
❌ violation of ISP example
class Worker: def work(self): pass def eat(self): pass class SuperWorker(Worker): def work(self): print("Working super hard!") def eat(self): print("Eating a super lunch!") class LazyWorker(Worker): def work(self): print("Working very little...") def eat(self): pass
Worker
”interface” defines eat
and sleep
methods, LazyWorker
impementation however, does not use it.✅ employment of ISP example
from abc import ABC, abstractmethod class Workable(ABC): @abstractmethod def work(self): pass class Eatable(ABC): @abstractmethod def eat(self): pass class SuperWorker(Workable, Eatable): def work(self): print("Working super hard!") def eat(self): print("Eating a super lunch!") class LazyWorker(Workable): def work(self): print("Working very little...")
We have 2 different “interfaces”
Workable
and Eatable
, both SuperWorker
and LazyWorker
implement only those interfaces that makes sense for them and they need. The Dependency Inversion principle (DIP)
High-level modules should not depend on low-level modules, both should depend on abstractions
❌ violation of DIP example
class LowLevelModule: def __init__(self): self.data = [] def get_data(self): return self.data class HighLevelModule: def __init__(self): self.llm = LowLevelModule() def process_data(self): data = self.llm.get_data() # Process data hlm = HighLevelModule() hlm.process_data()
Class
HighLevelModule
uses (depends on) LowLevelModule
instance to function properly. ✅ employment of DIP example
from abc import ABC, abstractmethod class DataStorage(ABC): @abstractmethod def get_data(self): pass class LowLevelModule(DataStorage): def __init__(self): self.data = [] def get_data(self): return self.data class HighLevelModule: def __init__(self, data_storage: DataStorage): self.ds = data_storage def process_data(self): data = self.ds.get_data() # Process data llm = LowLevelModule() hlm = HighLevelModule(llm) hlm.process_data()
Class
HighLevelModule
here accepts DataStorage
”interface”. This can be any class that implements it. Here it’s LowLevelModule
class creating the instance llm
.