The Solid Principle ποΈ is a set of five design principles in OOP that helps developers to create more maintainable, Flexible and Scalable software.
What is main focus of these principles? These principles aim to improve software design and encourage best practices.
So SOLID Principles is actually a 5 principles combined within "Solid".
Let's dive into them...!
1- S => Single Responsibility Principle (SRP
)
2- O => Open/Closed Principel (OCP
)
3- L => Liskov Substitution Principle (LSP
)
4- I => Interface Segregation Principle (ISP
)
5- D => Dependency Inversion Principle (DIP
)
These are the 5 Principles, Now it's time to through each one..
1.Single Responsibility Principle
Definition : A Class should have one reason to change, Meaning it should have only one functionality.
Benefit : Reduces complexity and improve code maintainability by ensuring each class handles a single function.
If you don't feel it makes sense, Check the next example, otherwise just pass it
//Violate SRP
public class UserManager {
public void AddUser(string Email , int Id) {
//Some code....
}
public void SendEmailToUser(int Id) {
//Some code....
}
public void SendReportToUser(string Email) {
//Some code....
}
}
Q1 : What do you think we should do to follow the SR (Single Responsibility) Principle ?.....
The last example use one class UserManager
to do more than one function (AddUser , SendEmailToUser , SendReportToUser). In order to follow the SRP, Each class should preform only one function .
//Following the SRP
public class UserManager{
public void AddUser(string Email , int Id) {
//Some code..
}
}
public class EmailService{
public void SendEmail(int Id) {
//Some code..
}
}
public class ReportService{
public void SendReport(string Email) {
//Some code..
}
}
2.Open/Closed Principle (OCP)
Definition : Software entities (Classes, Modules, Functions) should be able to extend, but not able to modify. So open for extension and closed for modification
Benefit : New functions can be added without alerting existing code, reducing the risk of introducing bugs..
Let's take an example of OCP violation
public class PaymentService {
public void PaymentProccess(string paymentType) {
if(paymentType == "PayPal") {
//Some code....
}
if(paymentType == "CreditCard") {
//Some code....
}
if(paymentType == "BitCoin") {
//Some code....
}
}
}
Q2 : What would you think the last example is breaking the OCP ?!
Let's now refactoring to OCP....
// #1: Create a interface
public interface IPaymentService {
void process();
}
// #2: Implement Payment Classes
public class PayPalPayment : IPaymentService {
public void Process() {
//Some code..
}
}
public class ICreditCardPayment : IPaymentService {
public void Process() {
//Some code..
}
}
In this way, we made our code following the OCP, How that ?!
Before factoring to OCP.. We must add a if statement to check the paymentType, After refactoring to OCP, we can easily just add a class for processing a paymentType without changing the base code..!
public class BitcoinPayment : IPaymentService {
public void Process() {
//Some code..
}
}
3.Liskov Subsitution Principle (LSP)
Definition: Objects of the superclass should be replaceable with objects of subclass without affecting the correctness of the program. In other words, Let's say you have a superclass called [A] and subclass called [B], you should be able to use [B] wherever the [A] Class is used..
Benefit: Maintainability.. Base code would not change or effect when you add external code or function.
//Superclass
public class Bird {
public virtual class Fly {
Console.WriteLine("This bird is flying");
}
}
//Subclass #1
public class sparrow : Bird {
public override void Fly() {
Console.WriteLine("The sparrow is flying");
}
}
//Subclass #2
public class Penguin : Bird {
//Surely penguins can't fly.. So we are passing incorrect info..
public override void Fly {
throw new NotImplementedException("Penguins can't fly!");
}
}
This violate the LSP... because if we use the penguin
class wherever the Bird
object is expected.. The program will behave unexpectedly, because calling a Fly
on a penguin
class will throw an exception... penguins can't fly buddy π!!
Q3: Can you try to figure out how i'm going to achieve LSP??
public abstract class Bird {
public abstract void Display();
}
//Create interface for flying behavior..
public interface IFlyable {
void Fly();
}
//Now we cant implement the subclass correctly...
public class sparrow : Bird, IFlyable {
public override void Display () {
Console.WriteLine("This is a sparrow!!");
}
public void Fly() {
Console.WriteLine("This sparrow is flying");
}
}
public class penguin : Bird {
public override void Display () {
Console.WriteLine("This is a pneguin!");
}
}
4. Interface Segregation Principle (ISP)
Defintion : The Interface Segregation Principle states that a class should not be forced to implement interfaces it does not use. Instead, larger interfaces should be divided into smaller, more specific ones so that implementing classes only need to be concerned with the methods relevant to them
Benefit: Prevents classes from implementing irrelevant methods. Makes code more modular and easier to understand. Supports the Single Responsibility Principle (SRP) by focusing interfaces on specific behaviors.
Violating the principle :
public interface IAnimal {
void Eat();
void Fly();
void Swim();
}
public class Dog : IAnimal {
public void Eat() {
Console.WriteLine("Dog is eating.");
}
public void Fly() {
throw new NotImplementedException(); // Not relevant for a dog
}
public void Swim() {
Console.WriteLine("Dog is swimming.");
}
}
The last example violates the Interface Segregation Principle (ISP) because it forces the Dog class to implement methods (Fly) that are irrelevant or unnecessary for its behavior. This creates several problems.
let's try to refactoring to the principle:
public interface IEater {
void Eat();
}
public interface ISwimmer {
void Swim();
}
public class Dog : IEater, ISwimmer {
public void Eat() {
Console.WriteLine("Dog is eating.");
}
public void Swim() {
Console.WriteLine("Dog is swimming.");
}
}
by splitting the IAnimal interface into smaller, behavior-specific interfaces like IEater, IFlyer, and ISwimmer, each class can implement only the interfaces relevant to its behavior. This avoids the issues mentioned above and adheres to the ISP, making the codebase cleaner, more maintainable, and more flexible
5.Dependency Inversion Principle (DIP)
Definition : The High-Level modules should not depends on the Low-Level Modules..Both should depend on abstract (Interface/Abstract Classes)..
Benifit : Reduce tight coupling between components, making the system more flexible and more easier to refractor or reset..
That's it for now! Keep coding and stay awesome. Catch you later!
Top comments (8)
Good Efforts on example. Pretty straight forward to understand π
Thank you! I'm glad you found the example straightforward and easy to understand π
You missed one.
Missing the "I" in SOLID
If I am not wrong you haven't explained "I" principle -> Interface Segregation Principle (ISP)
Good catch! Looks like I might have skipped the 'I' in SOLIDβI'll make sure to apply my own Interface Segregation Principle and focus on it in a dedicated section. Thanks for pointing it out!
I love the SOLI principals
Looks like I applied the Single Responsibility Principle a bit too literally and left one out! π Thanks for catching thatβIβll fix it faster than a Liskov substitution!