The SOLID Principles are five principles of Object-Oriented class design. They are a set of rules for certain design patterns and software architecture in general.
- S - Single responsibility principle
- O - Open-Closed principle
- L - Liskov substitution principle
- I - Interface segregation principle
- D - Dependency inversion principle
Single responsibility principle
A class should have only one job. If a class has more than one responsibility,
it becomes coupled. A change to one responsibility results to modification of
the other responsibility.
class FileReader:
def __init__(self, filename):
self.filename = filename
def read_file(self):
with open(self.filename, 'r') as file:
data = file.read()
return self.parse_data(data)
def parse_data(self, data):
# Parsing logic here
return parsed_data
Above example, the FileReader
class is violating the Single Responsibility Principle because it has two responsibilities: reading data from a file and parsing the data.
To fix this, we can create a separate class for parsing the data:
class FileReader:
def __init__(self, filename):
self.filename = filename
def read_file(self):
with open(self.filename, 'r') as file:
data = file.read()
return data
class DataParser:
def parse_data(self, data):
# Parsing logic here
return parsed_data
Open-Closed principle
Software entities (Classes, modules, functions) should be open for extension, not for modification.
class Animal:
def __init__(self, name: str):
self.name = name
def get_name(self) -> str:
pass
animals = [
Animal('lion'),
Animal('mouse')
]
def animal_sound(animals: list):
for animal in animals:
if animal.name == 'lion':
print('roar')
elif animal.name == 'mouse':
print('squeak')
animal_sound(animals)
Above example function animal_sound does not conform to the open-closed principle because it cannot be closed against new kinds of animals. If we add a new animal . We have to modify the animal_sound function.
To fix this we can create a new class for every animal which extend Animal and implement make_sound method.
class Animal:
def __init__(self, name: str):
self.name = name
def get_name(self) -> str:
pass
def make_sound(self):
pass
class Lion(Animal):
def make_sound(self):
return 'roar'
class Snake(Animal):
def make_sound(self):
return 'hiss'
def animal_sound(animals: list):
for animal in animals:
print(animal.make_sound())
animal_sound(animals)
Now, if we add a new animal, animal_sound doesn’t need to change. All we need to do is add the new animal to the animal array.
Liskov substitution principle
The Liskov Substitution Principle is a design principle that states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
class Animal:
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
def animal_sound(animal):
return animal.make_sound()
dog = Dog()
cat = Cat()
print(animal_sound(dog)) # Output: "Woof!"
print(animal_sound(cat)) # Output: "Meow!"
Dog
or a Cat
object to the animal_sound
function without affecting the correctness of the program. This is because both Dog
and Cat
are subtypes of Animal
, and they implement the same make_sound
method in their own way.
Interface segregation principle
The Interface Segregation Principle (ISP) is a design principle that states that no client should be forced to depend on methods it does not use. In other words, we should break interfaces into smaller, more specific interfaces so that clients only need to implement the methods they actually use.
class IAnimal:
def eat(self):
pass
def sleep(self):
pass
def make_sound(self):
pass
class Dog(IAnimal):
def eat(self):
return "Dog is eating."
def sleep(self):
return "Dog is sleeping."
def make_sound(self):
return "Woof!"
class Cat(IAnimal):
def eat(self):
return "Cat is eating."
def sleep(self):
return "Cat is sleeping."
def make_sound(self):
return "Meow!"
In above example class is forcing its clients to implement all three methods of the Animal
class, even if they only need to use one or two methods for training.
To fix this, we can break down the Animal
interface into smaller, more specific interfaces:
class IEatable:
def eat(self):
pass
class ISleepable:
def sleep(self):
pass
class IMakeSoundable:
def make_sound(self):
pass
class Dog(IEatable, IMakeSoundable):
def eat(self):
return "Dog is eating."
def make_sound(self):
return "Woof!"
class Cat(IEatable, ISleepable, IMakeSoundable):
def eat(self):
return "Cat is eating."
def sleep(self):
return "Cat is sleeping."
def make_sound(self):
return "Meow!"
we have broken down the Animal
interface into three smaller interfaces - IEatable
, ISleepable
, and IMakeSoundable
. The Dog
and Cat
classes now implement these smaller interfaces instead of the larger Animal
interface.
Dependency inversion principle
The Dependency Inversion Principle (DIP) is a design principle that states that high-level modules should not depend on low-level modules, but both should depend on abstractions. This means that instead of depending on concrete implementations, we should depend on abstract interfaces or classes.
Here's a simple example in Python to illustrate the Dependency Inversion Principle:
class IA:
def get(self):
pass
class A(IA):
def __init__(self):
self.a=[1,2,3,4]
def get(self):
return self.a
class B:
def __init__(self, obj:IA):
self.obj=obj
def get(self):
print(self.obj.get())
class C(IA):
def __init__(self):
self.b=[1,2,3]
def get(self):
return self.b
a=A()
c=C()
b1=B(c) # output : [1,2,3]
b2=B(a) # output : [1,2,3,4]
b.get()
we have introduced an abstract interface called ILightSource
that defines the turn_on
and turn_off
methods that the LightSwitch
class needs to use. The LightBulb
class now implements this interface instead of having its own methods.
Top comments (0)