Understand SOLID Principles and Replace Your Bad Code

One step to become better programmer

Fushi Art
Geek Culture
5 min readJan 13, 2021

--

Photo by USGS on Unsplash

Understanding SOLID principles is one of the worst nightmares for beginners. Today, I’ll explain it in the easiest way. Make sure you have basic Java knowledge before reading this article. Let jump into it!

But first, what is SOLID?

In object-oriented design, the SOLID principles (or simply SOLID) are a group of five design principles meant to make your code cleaner, more flexible, and easier to change. Each letter in SOLID presents a principle. We’ll cover one by one principle from S to D.

1. Single Responsibility Principle (SRP)

The first principle’s concept is: “Each class should only have one responsibility, do only one job, and that is the only reason a class should be changed.”

Do you get it? Let see this LocalWeather.java

In order to get the current temperature in the local address of users, first, it calls getLatitude() and getLongitude(). This class has to do 2 separate things:

  • find user location
  • find local temperature using that location

And because of that, it violates our first principle. To resolve it, we separate into 2 classes, each class do only one job.

Now we have two class and each class is responsible for doing one thing.

2. Open-Closed Principle (OCP)

The second principle is usually mention as:

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”

It means: if your code need extension (do more things, add a new class,…), it should be easy to do that without modify existed code. See this example and you will get it clearly

We have 2 class Cat and Dog, both extend Animal. And the Manager want to order a random animal to speak. Is that code good? Yeah, kind of…

NO, IT IS NOT.

Why it’s bad? Because every time we add a new animal, (Bird class extends Animal for example) we have to not only add new class, but also modify Manager class, and that really a bad thing. Imagine if you have to add 100 new animals.

We should create an abstract method in Animal and override it in every child.

Now every time we add a new animal, we no need to change Manager class.

3. Liskov Substitution Principle (LSP)

Many developers don’t understand and follow this rule. But once you got it, you will do it instinctively.

The LSP says: “functions that use references to base classes must be able to use objects of the derived class without knowing it”.

In simple words, derived classes must be substitutable for the base class. If it can’t be replaced, they shouldn’t have a father-son relationship. Take a look at the below code.

We can see the code follows the LSP perfectly (of course in the real projects, we have to check if a human is null and many things else) because if we replace a human with any child of it, the application will not break.

So what if we have a new class, named Baby? What will you do? Just make a new Baby.java and extends Human class? Do you see any problem with doing that? Yes, the baby can’t walk. So if we replace humans to babies, the code will throw an error. Therefore, you can’t create a baby class that extends Human.

One of solution for this is we can rename Human class to Adult. MiddleAge and Teenager are extend from Adult (I know teens are not adults but I can’t figure out any name better, maybe WalkableHuman?). And now Adult and Baby will extend Human.

4. Interface Segregation Principle (ISP)

This maybe is the easiest principle.

Class should not be forced to depend upon interfaces that they do not use”

You can also understand like you need to seperate one big interface into smaller one, and a class should only implements interfaces can be executed.

We have here an interface IShape and two abstract method calculateArea() and calculateVolume(). But many shape won’t need to calculate volume like Square, or Circle, only three dimension shape need that (e.g. an Orb). Therefore, make Square class implements IShape is kind of redundant. We should split IShape into 2 smaller Interface.

Now, 2D shape only need to implements IShape2D (only need to override calculateArea()). If you want all 3D shape calculate area, you need to make them implement 2 different interfaces, or you can make IShape3D extends IShape2D.

5. Dependency Inversion Principle (DIP)

Finally, the last one. This one is not hard to follow but really hard to understand at first place, especially for beginner.

“Classes must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.”

So what is high-level and low-level module? Briefly, low-level module are like File class or Record class, and high-level class are FileManager or MediaManager. The low-level module usually are high-level module property. To understand better, see this example first.

The Boss are high-level module and Employee are low-level module. The boss can assign work to employee if he knows employee’s id, then the employee executes him/her task. You can ignore giveAccessToEmployee() method for now.

In some case, in the middle of working process, the employee realizes he/she have to do other things in order to finish the task (e.g. go out side, to warehouse, access to limited resource,…), he/she need to ask for permission from the boss, or in other word, need the boss object call giveAccessToEmployee() method in the middle of the work (inside doWork method). How do you do that? Beginners usually make their mistake here. They put the boss itself to the doWork method like this

Some beginners even make boss became Employee property. It’s really bad codebase. ’Cause if you give your low-module fully access to the high-module, the employee can order the boss to do everything. Imagine a File object can delete all other files. You really don’t want this happen, who knows someday you log in your application and everything is gone because you give the permission to delete data to too many objects.

So, how can we avoid that but still give employee the right to ask the boss? Using Interface. We will make the boss implement IBoss interface, which only have one method giveAccessToEmployee. Now the employee will take IBoss as a property.

The complete example are shown above. When employee class only has iBoss not the Boss as property, iBoss can’t call other methods except giveAccessToEmployee.

Hope this article has cleared your mist. Understand SOLID is hard, but compliancing them even much harder. Keep practicing and some day seniors who look at your codebase will say “That’s a good one!”.

--

--