What are SOLID Design Principles?
SOLID is one of the most popular set of design principles that are used in object oriented programming. SOLID is the acronym which gives us five principles.
Why do we need these principles?
Changes are inevitable for any successful software. But as they change, they become increasingly complex. Without a good design, programming becomes rigid and immobile. SOLID design principles were designed to address and avoid such issues.
The main goal of these principles is to design a loosely coupled framework, which is easy to understand, maintain and extend. One area should be easy to modify without impacting others. These principles are the basis for agile to make adaptive softwares.
When the code is to the point and easy to understand, it becomes easier to locate and fix the bugs.
Let us understand these principles one-by-one.
Single Responsibility Principle
This principle dictates that each class/module should have one, and only one responsibility.
If a class is responsible for more than one functionality, it becomes difficult to find root cause of a problem and it can destabilize other areas of code too, giving rise to more bugs. Simpler entities are easy to understand and maintain, increasing project longevity. It also helps in bug isolation and impact evaluation.
Let us understand this with an example. Suppose we have a class Area as below -
In the above code, we can see that Area class has three responsibilities to Calculate the area, Print the details and then to Calculate the perimeter. Following Single Responsibility principle, all three methods can be added to separate classes so that each class has only one responsibility.
This principle states that each class/module should be open to extension, but closed for modifications.
The idea here is to say that, a well tested class will need to be modified for any new additions. But such modifications can bring in issues/bugs. So, instead of modifying a class, it should be extended and methods can be overwritten. This makes the code maintainable and revisable.
Below example explains it. Suppose class Shape has a method to calculate area as below. Circle and Square classes extend class Shape.
Now, if we want to add another class Rectangle that extends class Shape, then we will have to add another 'if condition' to the CalculateArea method for rectangle. This does not look like a good practice for big softwares and against the principle of Open-Closed.
So, a better approach would be to have classes Circle and Square override the method CalculateArea. Each can have their formula for area calculation. This way we can have as many classes extending class Shape without modifying the class Shape itself.
Liskov Substitution Principle
According to this principle, object of a derived class should be able to replace an object of the base class without bringing any errors in the system.
Though this principle is difficult to internalize, but essentially it is an extension of Open-Closed Principle, as it ensures derived class extends the base class without modifying its behavior.
For example, class WinampMediaPlayer extends class MediaPlayer as below -
Here we can see that class WinampMediaPlayer can't replace class MediaPlayer, as WinampMediaPlayer does not support video. This is against the Liskov Substitution Principle. So having separate classes for audio and video can be considered.
Interface Segregation Principle
The idea of this principle is to have a lot of smaller interfaces than a few bigger ones. Interfaces should be fine grained and client-specific.
A client should not be forced to implement the functionalities that they don't need. For a new functionality for a client, a developer should not add new methods to an existing interface. Instead it is better to create a new interface with those methods. This way the clients implementing the older interface won't be impacted. Smaller interfaces would mean that developers can have preference of composition over inheritance and decoupling over coupling.
In the example below we can see that class Bike extends class Vehicle. But some of the methods in class Vehicle are not valid for class Bike, which is against Interface Segregation Principle. A separate interface should be created for additional functionality.
Dependency Inversion Principle
This principle states that High level modules should not depend on Low level modules, but should depend on Abstractions. Also, Abstractions should not depend on details, details should depend on Abstractions.
Below example explains this.
This code will work fine as such. But what if we want one diesel and one petrol engine, then we need to modify the class Car. But we can easily fix this problem by creating Engine as an interface as below. We do not need to modify the class Car.
Advantages of SOLID principles
Loosely coupled code - Modification of one class does not affect others.
Code is easy to understand.
Code becomes more readable, testable, scalable and reusable.
Good design practice.
To create good code that is competitive and meet industry standards, using SOLID design principles becomes imperative. While implementing these principles can be overwhelming at first, but with practice and careful comparison between the usage can make things easier.