SOLID Principles in Software Design
4 min readMar 16, 2024
SOLID is an acronym representing five fundamental principles of object-oriented design and programming. These principles aim to make software designs more understandable, flexible, and maintainable. Let’s explore each principle in detail.
S: Single Responsibility Principle (SRP)
- A class should have only one responsibility or reason to change.
- It should not be filled with excessive functionality.
- Example: If you have a
Student
class withsendEmail
andenrollCourse
methods, these services should be in separate classes likeEmailService
andCourseEnrollmentService
. This way, the code becomes more reusable. - Note: Single Responsibility does not mean a single job. It means that everything the class does should be closely related.
// Violates SRP
public class Student
{
public void EnrollCourse(Course course) { /* ... */ }
public void SendEmail(string email, string body) { /* ... */ }
}
// Follows SRP
public class Student
{
public void EnrollCourse(Course course) { /* ... */ }
}public class EmailService
{
public void SendEmail(string email, string body) { /* ... */ }
}
O: Open/Closed Principle (OCP)
- Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
- You should be able to add new functionality without changing existing methods.
- One way to achieve this is through the Decorator pattern or Extension Methods.
// Violates OCP
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
// Follows OCP
public interface ICalculator
{
int Calculate(int a, int b);
}public class Addition : ICalculator
{
public int Calculate(int a, int b)
{
return a + b;
}
}public class Multiplication : ICalculator
{
public int Calculate(int a, int b)
{
return a * b;
}
}
L: Liskov Substitution Principle (LSP)
- Subtypes must be substitutable for their base types.
- Child classes should be able to do everything that parent classes can.
- Example: If we create a
Human
class and aParent
class, aChild
class should be able to do everything that aParent
can do.
// Violates LSP
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area()
{
return Width * Height;
}
}public class Square : Rectangle
{
public override int Width
{
set { base.Width = base.Height = value; }
} public override int Height
{
set { base.Width = base.Height = value; }
}
}// Follows LSP
public abstract class Shape
{
public abstract int Area();
}public class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; } public override int Area()
{
return Width * Height;
}
}public class Square : Shape
{
public int Side { get; set; } public override int Area()
{
return Side * Side;
}
}
I: Interface Segregation Principle (ISP)
- Clients should not be forced to depend on interfaces they do not use.
- Large interfaces should be divided into smaller ones.
- The Repository pattern helps to build this.
// Violates ISP
public interface IWorker
{
void Work();
void EatFood();
void Sleep();
}
public class Human : IWorker
{
public void Work() { /* ... */ }
public void EatFood() { /* ... */ }
public void Sleep() { /* ... */ }
}public class Robot : IWorker
{
public void Work() { /* ... */ }
public void EatFood() { /* Not applicable */ }
public void Sleep() { /* Not applicable */ }
}// Follows ISP
public interface IWorker
{
void Work();
}public interface ILivingBeing
{
void EatFood();
void Sleep();
}public class Human : IWorker, ILivingBeing
{
public void Work() { /* ... */ }
public void EatFood() { /* ... */ }
public void Sleep() { /* ... */ }
}public class Robot : IWorker
{
public void Work() { /* ... */ }
}
D: Dependency Inversion Principle (DIP)
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
- Example: If a
ServiceClass
needs to call something in the database, instead of creating a new repository class directly inside the service (which would make the service depend on a low-level component), you can create a repository interface inside the service class.
// Violates DIP
public class ServiceClass
{
private DatabaseRepository _repository = new DatabaseRepository();
public void DoSomething()
{
// Use _repository to access data
}
}// Follows DIP
public interface IRepository
{
void SaveData(object data);
object GetData(int id);
}public class ServiceClass
{
private readonly IRepository _repository; public ServiceClass(IRepository repository)
{
_repository = repository;
} public void DoSomething()
{
// Use _repository to access data
}
}public class DatabaseRepository : IRepository
{
public void SaveData(object data) { /* ... */ }
public object GetData(int id) { /* ... */ }
}
By following the SOLID principles, you can create software designs that are more maintainable, extensible, and robust. These principles promote code reusability, testability, and flexibility, making it easier to adapt to changing requirements and evolving technologies.