SOLID Principles: The Secret to Crafting Robust and Future-Proof Software
Tags: csharp, solidprinciples, developer, programming
Published: 2023-02-13 13:24:29

I am pretty sure whoever reads this article heard “**SOLID**” at least one time in their life. It is an acrostic of 5 set of rules which should known to have a robust and future-proof software.

Let’s dive into SOLID Principles a little bit more 👇

**SOLID** is a collection of five design guidelines that aid programmers in producing software that is simple to update and maintain over time. These guidelines help teams build and add new features to existing software systems without having to worry about unintended consequences or breaking existing functionality. Software developers all over the world have largely adopted the **SOLID** principles, which offer recommendations for writing clear, maintainable, and scalable code.

To put it simply; **SOLID** principles work to make sure that software systems are well-organized, simple to comprehend and less prone to errors and bugs.

As i said in my introduction, SOLID is an acrostic term;
**S**ingle Responsibility Principle (SRP):
A class should have just one job, which means it should have just one reason to change.

**O**pen/Closed Principle (OCP):
Classes, modules, functions, and other software entities should be open to extension but closed to modification.

**L**iskov Substitution Principle (LSP):
A superclass’s objects should be interchangeable with a subclass’s objects without affecting the program’s correctness.

**I**nterface Segregation Principle (ISP):
The dependence of clients on interfaces that they do not use should not be enforced.

**D**ependency Inversion Principle (DIP):
Low-level modules shouldn’t be a dependency of high-level modules. Both need to be based on abstractions.

### Single Responsibility Principle (SRP)

Consider having a teacher who is in charge of several subjects. You must substitute teachers if the teacher’s position changes, such as if they begin teaching a new subject. However, if the teacher only covered a single subject, you would only need to have them replaced if that particular subject changed. Similar to how a computer program should only perform one task, any additional tasks should be written as separate programs.

C# Example;
```
public class User
{
	public int UserId { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string Email { get; set; }

	public void UpdateUser(string firstName, string lastName, string email)
	{
		this.FirstName = firstName;
		this.LastName = lastName;
		this.Email = email;
	}
}

public class UserValidator
{
	public bool ValidateUser(User user)
	{
		// Validation logic
		return true;
	}
}
```

In this example, the `User` class has only one responsibility, which is to store information about a user. The `UserValidator` class has the responsibility of validating a user. This separation of responsibilities makes it easier to maintain and test the code.

### Open/Closed Principle (OCP)

Imagine a Lego block. A lego block cannot be altered after it has been created; however, you can add blocks to it to make it larger. Similar to how a computer program should be created, it should allow for future additions without requiring changes to the original code.

C# Example;
```
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
{
	private int _side;

	public override int Width
	{
		get { return _side; }
		set { _side = value; }
	}

	public override int Height
	{
		get { return _side; }
		set { _side = value; }
	}
}

public class AreaCalculator
{
	public int CalculateArea(Rectangle[] shapes)
	{
		int area = 0;
		foreach (var shape in shapes)
		{
			area += shape.Area();
		}
		return area;
	}
}
```

In this example, the `Rectangle` class can be extended to create new shapes, such as `Square`, without changing the original code. The `AreaCalculator` class can be used to calculate the area of an array of shapes, regardless of the specific type of shape.

### Liskov Substitution Principle (LSP)

Think of a light bulb that you can insert into a lamp. If you purchase a different light bulb, it ought to function just as well as the original light bulb as long as it fits into the lamp. A computer program should be created in a similar manner, allowing you to swap out any component as long as the new component continues to function in the same manner as the original.

C# Example;
```
public interface IShape
{
	int Area();
}

public class Rectangle : IShape
{
	public int Width { get; set; }
	public int Height { get; set; }

	public int Area()
	{
		return Width * Height;
	}
}

public class Square : IShape
{
	public int SideLength { get; set; }

	public int Area()
	{
		return SideLength * SideLength;
	}
}

public class AreaCalculator
{
	public int CalculateArea(IShape[] shapes)
	{
		int area = 0;
		foreach (var shape in shapes)
		{
			area += shape.Area();
		}
		return area;
	}
}
```

In this example, the `Rectangle` and `Square` classes both implement the `IShape` interface, and the `AreaCalculator` class can calculate the area of an array of shapes, regardless of the specific type of shape. This allows us to substitute a derived class for a base class without affecting the correctness of the program, as long as the derived class follows the same contracts as the base class.

### Interface Segregation Principle (ISP)

Think of a toolbox as having a wide variety of tools inside. Some tools you never use, while others you use frequently. It would be better if you could just keep your actual tools in a toolbox. Similar to how a computer program should be created so that you only use the features that you actually require.

C# Example;
```
public interface IWorker
{
	void Work();
	void Eat();
}

public interface IWorkable
{
	void Work();
}

public interface IFeedable
{
	void Eat();
}

public class Worker : IWorkable, IFeedable
{
	public void Work()
	{
		// Work logic
	}

	public void Eat()
	{
		// Eat logic
	}
}

public class Robot : IWorkable
{
	public void Work()
	{
		// Work logic
	}
}
```

In this example, the `IWorker` interface has methods for both working and eating. However, this can lead to classes implementing methods that they don't need. To solve this, the IWorker interface is separated into two smaller interfaces, `IWorkable` and `IFeedable`. Classes that only need to work, like the Robot class, can implement only the `IWorkable` interface.

### Dependency Inversion Principle (DIP)

Consider you are constructing a home. Everything else is constructed on top of the foundation, which is the most crucial component of the house. The rest of the house will be strong if the foundation is sturdy. Similar to this, the most crucial sections of a computer program ought to be written first, and everything else ought to be built on top of them. In this manner, everything will continue to function even if you need to change just the essential components.

C# Example;
```
public interface IRepository<T>
{
	T GetById(int id);
	void Save(T entity);
}

public class SqlRepository<T> : IRepository<T>
{
	public T GetById(int id)
	{
		// Get from database logic
		return default(T);
	}

	public void Save(T entity)
	{
		// Save to database logic
	}
}

public class Service<T>
{
	private readonly IRepository<T> _repository;

	public Service(IRepository<T> repository)
	{
		_repository = repository;
	}

	public T GetById(int id)
	{
		return _repository.GetById(id);
	}

	public void Save(T entity)
	{
		_repository.Save(entity);
	}
}
```

In this example, the `Service` class depends on an abstraction, the `IRepository` interface, rather than a concrete implementation, such as the `SqlRepository`. This makes it easier to switch out the underlying repository implementation if needed, without affecting the `Service` class. The `Service` class also uses constructor injection to receive the repository implementation, making the dependency explicit and easier to manage.

In conclusion; the **SOLID** principles are an important part of software development, assisting programmers in building systems that are extensible, maintainable, and simple to change. By adhering to these guidelines, software systems can be created to avoid common pitfalls like rigid structures and tightly coupled code. I hope the examples in C# have been useful in helping you gain a better understanding of the **SOLID** principles by showing how they can be used in practical situations.

I hope that this article has been informative and helpful, and I encourage you to continue exploring and learning more about the wonderful world of software development. 🤗

*With the power of SOLID principles, the sky truly is the limit for what we can create. 🌌*