What are SOLID Principles?
SOLID Principles are design principles to make software design extendable, readable and maintainable in long run.
SOLID Acronym
S – Single Responsibility Principle
O – Open-Closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle
Now lets understand the each principle with an example to clear the concept.
Single Responsibility Principle :
Each class or module should have only one responsibility. Responsibility means related behaviors of the class.
Example: Below design violates SRP.
interface IUser
{
bool Login(string uname, string password);
bool Register(object user);
bool SaveUser(object user);
bool DeleteUser(object user);
bool SaveDepartment(object department);
}
In above design IUser interface has responsibilities related to user (non highlighted method declarations) as well as department (highlighted method declaration) which clearly violates ‘one responsibility’ principle.
Solution
interface IUser
{
bool Login(string uname, string password);
bool Register(object user);
bool SaveUser(object user);
bool DeleteUser(object user);
}
interface IDepartment
{
bool SaveDepartment(object department);
}
Now each interface IUser and IDepartment has exactly only one responsibility to perform which makes it a perfect class design to maintain and scale the solution.
Open-Closed Principle:
Any class or module should be open for extension and closed for modification. Meaning solution has to be designed and written in a way that same code need not to be changed every time requirement changes.
Example: Below code violates open/closed principle.
Requirement: Calculate the area of triangle
class CalculateArea
{
public double width { get; set; }
public double length { get; set; }
double Area()
{
return width * length;
}
}
Requirement: Calculate the area of circle. In below code we can notice that existing code has been modified as per changed requirement which violates OCP.
class CalculateArea
{
public double width { get; set; }
public double length { get; set; }
public double radius { get; set; }
double Area(string shape)
{
if (shape == "rectngle")
return width * length;
else
return Math.PI * radius * radius;
}
}
So the question is now what can be the solution of this design issue? Please see the below example.
Solution
abstract class Shape
{
abstract double area();
}
class Rectangle : Shape
{
double width { get; set; }
double height { get; set; }
override double area()
{
return width * height;
}
}
class Circle : Shape
{
double radius { get; set; }
override double area()
{
return Math.PI * radius * radius;
}
}
In above example we have used abstract class to have a common method area() which can be inherited by n child classes as per requirement.
In this case to calculate the area of Rectangle we can have a child class which inherits the parent class Shape and override the method to calculate the area. Now if the requirement comes to calculate the area of Circle as well then we need not modify the existing implementation rather extend it. We can add one more child class Circle which again inherits parent class Shape and override the method area to calculate the area hence designing the solution preserving the OCP.
Real World Example:
Lets say we have a gateway payment method that supports RazorPay. Now we want to integrate Stripe and PayPal as well. What should be the approach? Modifying existing method definately will not be the good idea hence we should create an interface IPaymentGateway with a ProcessPayment() method and let each class RazorPay, PayPal, Stripe implement it separately centralising the core business logic.
Liskov Substitution Principle:
LSP states that in a class design child class object should be able to substitute the parent class object without altering the output of design.
Example: Below code violates LSP.
public class Parent
{
public virtual void print()
{
Console.WriteLine("Parent class!");
}
}
public class Child : Parent
{
public override void print()
{
Console.WriteLine("Child class!");
}
}
class Program
{
static void Main(string[] args)
{
Parent parent = new Parent();
parent.print();
Console.WriteLine("***---Finihed---***");
Console.ReadKey();
}
}
Above code yields below output.

Now as per LSP lets substitute parent object with child object and we should still get the same output. Lets see what happens now. As we can see program does not yield same output hence violates LSP.

Solution
There are two ways to design this solution following LSP either using abstract class or interface. We will be using abtract class to design the solution.
public abstract class Grandparent
{
public abstract void print();
}
public class Parent : Grandparent
{
public override void print()
{
Console.WriteLine("Parent class!");
}
}
public class Child : Grandparent
{
public override void print()
{
Console.WriteLine("Child class!");
}
}
class Program
{
static void Main(string[] args)
{
Grandparent parent = new Parent();
parent.print();
Grandparent child = new Child();
child.print();
Console.WriteLine("***---Finihed---***");
Console.ReadKey();
}
}

Now you can see the result of this class design solution as even after substituting parent object with child object it does not change the output.
Interface Segregation Principle:
This principle states that no client code should be forced to depend on the methods which it does not use.
Meaning no class should be forced to implement methods it does not need rather a fat interface should be split in multiple smaller interfaces and implemented.
Example:
interface IStudent
{
void Save();
void GetAll();
void GetById(string id);
void SaveDepartment();
void GetDepartmentById();
void GetDepartmentByName(string name);
}
class Student : IStudent
{
public void GetAll()
{
throw new NotImplementedException();
}
public void GetById(string id)
{
throw new NotImplementedException();
}
public void GetDepartmentById()
{
throw new NotImplementedException();
}
public void GetDepartmentByName(string name)
{
throw new NotImplementedException();
}
public void Save()
{
throw new NotImplementedException();
}
public void SaveDepartment()
{
throw new NotImplementedException();
}
}
In above example Student class is forced to implement Department related methods which are not relevent to it.
Solution
Solution is to split the interface in multiple relevent interfaces. Here we split IStudent interface in two separate interfaces IStudent will have student related methods and IDepartment interface will have department related methods. So while implementing IStudent interface Student class need not tdepend on department related methods.
interface IStudent
{
void Save();
void GetAll();
void GetById(string id);
}
interface IDepartment
{
void SaveDepartment();
void GetDepartmentById();
void GetDepartmentByName(string name);
}
class Student : IStudent
{
public void GetAll()
{
throw new NotImplementedException();
}
public void GetById(string id)
{
throw new NotImplementedException();
}
public void Save()
{
throw new NotImplementedException();
}
}
Dependency Inversion Principle:
As per DIP high level modules should not be dependent on low level module. Both should depend on abstraction. Anything that calls something is high level module. I.e. If main() calls another service then main() is high level and service is low level module.
Dependency Inversion is a principle which is implemented through Dependency Injection.
Example:
public class EmailNotification
{
private SmtpClient _smtpClient;
public EmailNotification()
{
_smtpClient = new SmtpClient();
}
public void Send(string email, string message)
{
_smtpClient.Send(email, message);
}
}
In above example high level module EmailNotification is directly dependent on low level module SmtpClient rather than abstraction meaning both are tightly coupled which clearly violates DIP.
Solution
public interface IEmailSender
{
void Send(string email, string message);
}
public class SmtpClient : IEmailSender
{
public void Send(string email, string message)
{
//implementation
}
}
public class EmailNotification
{
private IEmailSender _emailSender;
public EmailNotification(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public void Send(string emailAddress, string message)
{
_emailSender.Send(emailAddress, message);
}
}
Now in above example high level module EmailNotification is dependent on abstraction IEmailSender rather being tightly coupled with low level module SmtpClient to acess its methods. IEmailSender is being injected to EmailNotification via constructor injector to access SmtpClient’s methods and this process is called dependency injection. So we can say we implement DIP via dependecny injection.
This is it about SOLID principles. Hope I was able to explain in brief yet in enough detail to make readers understand. I welcome any comment or feedback.
