5 min to read
Knowing SOLID helps you become a better developer
During years of studying, almost all students are taught some basic OOP concepts as follows:
- Abstraction
- Encapsulation
- Inheritance
- Polymorphism
These concepts have been introduced clearly and almost every interview has questions about OOP concepts. These 4 concepts are quite basic, you can google it to learn more.
The principles I introduce in this article are the design principles in OOP. These principles are drawn by dozens of developers and from thousands of successful and failed projects. Any project that applies these principle will have more readable, testable and maintainable code base. If you have experience working in the software development industry, you probably notice that the coding time only takes 20%-40% of your time, the rest of the time is to maintain the code: fixing bugs, refactor, etc. Mastering these principles and apply them to your code will help you take one extra step on the path of becoming senior developer.
SOLID principles can be break down into 5 different principle:
- Single responsibility
- Open/close
- Liskov substitution
- Interface segregation
- Dependency inversion
Single responsibility
A class should have one, and only one, reason to change
Think about this case, let’s say you have a class look like this
public class Shape{
public static void calculateSquareArea();
public static void calculateRectangleArea();
public static void calculateCircleRadius();
}
What have you noticed in the above example? The class Shape is responsible for 3 different things. In the future, when the requirements change, the class will need to be modified. The more responsibility of a class, the more changes request it will get, eventually it will make those changes harder to implement because you need to modify the class.
Instead you should separate them into several small classes. For example, the changes might be made like this:
public class Square{
public static void calculateArea();
}
public class Circle{
public static void calculateRadius();
}
Open-Close
You should be able to extend a class’s behaviour, without modifying it.
In this principle, the class’s behaviour should be able to be extended. Why? Because when there are requirements changes, you should be able to change the way of how the class behave in a different way in order to meet those requirements.
But also, closed for modifications. No one is allowed to make changes to the original class. The best way to achieve this is through inheritance and abstraction. In this way you would be able to change the behaviour of the class without modifying the class base. For instance, given the above example:
public abstract class Shape{
public abstract int calculateArea();
}
public class Square extends Shape{
public int calculateArea(){
return 10;
}
}
Liskov substitution
Derived classes must be substitutable for their base classes
Okay, things are getting complicated. Let me give you an explanation through examples, let say you have the parent class called Shape and the following child classes Square, Circle, Rectangle. Okay, if you inherit class Shape, the Square and Rectangle should be able to run smoothly. Mathematically saying, the area of square and rectangle needs the width and height. But class Circle may not be able to run smoothly, because the area of circle doesn’t need those factors and will cause errors.
![]()
As suggested, each method should have preconditions and postconditions. The preconditions must be true in order to execute the method and the postconditions must be true after the execution.
Interface segregation
Clients should not be forced to implement interface they do not use.
This principle is quite easy to understand. It is better to have many smaller interfaces. Instead of making an interface with 100 methods, you should break it down to several interface. Similar to Single responsibility. Because you don’t even use all of those. For example, let’s say you have an interface called Animal, and it has walk, eat methods. However some animals can fly as well, which means you will need to break it down by role, for example, you might want to break it down into CanFly interface, responsible for animals that can fly. Through that, the code will become more maintainable and readable.
interface Animal{
public void walk();
public void sleep();
}
public class Dog implements Animal{
public void walk(){
}
public void sleep(){
}
}
public class Bird implements Animal{
public void walk(){
// No need
}
public void sleep(){
}
}
Dependency inversion
High level modules should not depend upon low level modules. Both should depend upon abstraction
Abstractions should not depend upon details. Details should depend upon abstractions
This principle is quite important, often can be solved by using dependency injection. Dependency injection technique is injecting dependency of a class through constructor.
Without dependency injection
public class Animal{
public Head head = new Head();
public Tail tail = new Tail();
}
With dependency injection
public class Animal{
public Head head;
public Tail tail;
public Animal(Head head, Tail tail) {
this.head = head;
this,tail = tail;
}
}
These SOLID principles will make your projects maintainable, readable, testable and more. Hopefully in the future, I can write some post about each of these concepts in details, especially about dependency injection.
Comments