Building Rock-Solid Software with the SOLID Principles: Your Blueprint for Code Excellence!
- amol ankit
- Nov 8, 2023
- 2 min read

SOLID principles are a collection of 5 different practices which should be followed if you are to create a robust and reliable software code base/classes.
These five principles serve as a foundation for comprehending the significance of specific design patterns and software architecture as a whole. Consequently, I firmly hold the belief that they constitute essential knowledge for every developer.
Below I will be trying to explain each of the principles one by one so that you can understand and will feel a bit more confident while implementing them in your project.
Background
SOLID principles were introduced by Robert C. Martin, often referred to as "Uncle Bob."
Uncle Bob is also the author of the bestselling books "Clean Code" and "Clean Architecture".
NB: There is no specific order to the principles it can be DOLIS or DILOS.
Details of each SOLID principle
Single Responsibility Principle
Single Responsibility Principle: This principle states that a class should only have one reason for change and
should only be doing one thing and one thing alone.

For those who don't know above image is of a Swiss knife a multipurpose tool, no doubt that this is a very useful tool if you are camping or doing some similar activity.
However, the software development world is a bit different from our normal world. In the software development world the simpler the better, and you should have specific tools for specific purposes.
How does it work?
Let's see with example.
public class Book
{
private string _bookName;
private string _authorName;
private string _text;
// Remaining Implementation
...
...
//
public string ReplaceText(string word, string replacementWord)
{
return _text.Replace(word, replacementWord);
}
public boolean WordPresentInBook(string word)
{
return _text.contains(word);
}
public void PrintText()
{
Console.WriteLine(_text);
}
}
Can you guess what is the issue here?
Now at first glance, the class above seems fine and there are no issues with it.
But If you guessed the PrintText method is the violation then you are absolutely correct and you get the crux of this principle.
For those who didn't, Let's explore it in detail.
Consider this scenario, what if in future you want to print the text of the book via. method call to a printer or want to print the text with some other medium?
In the context of the Book class, it should only deal with stuff related to the core functionality of a book.
So, how can we fix this?
It's simple just remove the logic of printing the text to a separate class which only deals with Books IO operation and keep the core implementation in the Book class.
public class PrintBook
{
// Constructor and rest of the implemntation
public void PrintTextToConsole()
{
// Implementation
}
}
Open Closed Principle
Open-Closed Principle: This principle states the class should be Open for extension but closed for modification. If you understood it by just this line then excellent.
For more people like me below is its explanation.
Modification means doing code changes in an existing class to add new features or functionality.
OCP does not allow that however there is one exception, if you are trying to fix a bug in an existing system you can modify the class.
The extension part of the principle states that we should be able to add new features to the existing class without changing the original class itself.
Now how does this work?
Here is an example.
public class Guitar {
private string _make;
private string _model;
private double _prize;
//...
}
Now this class is working fine for the implementation, but as time progressed people came up with Electronic Guitar and there was a need to add the details of output jacks to the class.
If we have to follow the OCP we need to extend this class and create a new one rather than going in the main class and changing the code.
Here is what the new class should look like.
public class ElectronicGuitar : Guitar
{
// Implementation of specific requirement by Electronic guitar.
private double _outputJackDiameter;
}
By doing this we have all the implementation of the Guitar class and we have a new class specific to Electronic Guitar.
Liskov substitution Principle
Liskov's principle is easy to understand but hard to detect in code. So let's look at an example.
The Liskov Substitution Principle states that subclasses should be substitutable for their base classes.
What does this mean?
Given that class B is a subclass of class A, we should be able to pass an object of class B to any method that expects an object of class A and the method should not give any weird output in that case.
Below is a basic Rectangle class
public class Rectangle
{
private int _width;
private int _height;
public Rectangle()
{
}
public Rectangle(int width, int height)
{
_width = width;
_height = height;
}
public int GetWidth() => _width;
public void SetWidth(int width)
{
this._width = width;
}
public int GetHeight() => _height;
public void SetHeight(int height)
{
this._height = height;
}
public int Area() => _width * _height;
}
Now I want to have a special class for Square, which we can do by extending the class for Rectangle.
public class Square : Rectangle
{
public Square()
{
}
public Square(int size)
{
base.width = base.height = size;
}
public override void SetWidth(int width)
{
base.SetWidth(width);
base.SetHeight(width);
}
public override void SetHeight(int height)
{
base.SetHeight(height);
base.SetWidth(height);
}
}
In the above code, we made sure that SetHeight and SetWidth are implemented in such a way that whoever uses this Square class should not change height or weight in a way that can violate the square property.
By doing this we have violated the Liskov Substitution Principle, you may be asking how.
Let's see
public class Test
{
public void TestingLSP(String[] args)
{
Rectangle sq = new Square();
sq.SetWidth(5);
getAreaTest(sq);
}
}
Now in the above code, according to the principle, the instance creation where a Rectange class type is given an instance of a square class should not give us any wired error.
But this class will give an error because the SetWidth method call as it goes to the implementation written in the Square class is also updating the Height of the rectangle and will behave like a square.
Interface Segregation Principle
Interface Segregation Principle: The meaning of segregation is to separate things and interface segregation means separating interfaces.
This principle states that we should not create an interface with a lot of contracts in it which is then not used by some of the classes and has to throw NonImplementedException to comply with the interface.
In other words, separate small interfaces are better than single giant ones.
Let's see an example
public interface IParking
{
void ParkCar();
void CalculateCharge();
void Payment();
void RemoveCar();
}
Now the above interface can be used to create a Car Parking class but there may be a case where we need to implement a Free Car parking class, in that scenario we may need to throw errors for the Payment method like below.
public class FreeParking : IParking
{
// Remaining interface member
// ...
// ...
//
public void Payment()
{
throw new Exception("Can't pay for free parking");
}
}
To fix this we can segregate the Payment method into its interface and will only use that in classes where we need the Payment method to be implemented.
Dependency Inversion Principle
The principle of dependency inversion refers to the decoupling of software modules. Instead of high-level modules depending on low-level modules, both will depend on abstractions.
In an article from 2000, Uncle Bob summarizes this principle as follows:
"If the OCP states the goal of object oriented architecture, the DIP states the primary mechanism".
Consider this class
public class SendMail
{
private Logger _logger;
public void SendMail()
{
_logger = new Logger();
}
// Remaining implementation
}
This class seems fine and there is no apparent issue with this, Right?
Actually no, by creating a new instance of Logger in the constructor or in case anywhere in the class you are forcing the SendMail class to understand the process of creating the instance of the Logger class, so now your high-level class "SendMail" is dependent on low-level class "Logger".
To fix this issue we can rewrite the class as below. By doing this we are decoupling the SendMail and Logger classes as the SendMail class no longer needs to know how to instantiate the Logger class.
public class SendMail
{
private Logger _logger;
public void SendMail(Logger logger)
{
_logger = logger;
}
// Remaining implementation
}
Conclusion
In conclusion, the SOLID principles are not just a set of abstract guidelines; they are fundamental pillars that underpin good software design. By adhering to these principles, developers can create code that is clean, maintainable, and adaptable. In the ever-evolving world of software development, SOLID provides a reliable framework for building robust and efficient systems, ultimately leading to higher-quality software, improved collaboration among development teams, and greater customer satisfaction.
As you apply these principles in your software development journey, remember that they are not rigid rules but rather flexible guidelines to help you make informed design decisions. The SOLID principles empower you to write code that can stand the test of time and evolve with changing requirements. They are an invaluable asset for any developer looking to elevate their skills and craft software that not only works but works well.
Comments