SOLID Design Principles In Kotlin
Before we jump onto the topic of SOLID design principles, you must understand why we need them in the first place. If you are hearing the term, SOLID, for the first time then sit tight as you’ll learn a whole new way of designing your classes.
What problems we are trying to solve using SOLID principles?
Let me try to answer the most important question.
How many of you have been slowed down by really bad code? All of us at some point.
If we know that bad code slows us down then why do we write bad code?
We do it as we had to go fast ….and let that sink in.
As Uncle Bob, Robert C. Martin says
You don’t go fast by rushing.
You don’t go fast by just making it work and releasing it as fast as you can.
You wanna go fast? You do a good job.
You sit carefully, think about the problem, type a little, clean it up and repeat. That’s how you go fast.
What are the symptoms of bad code?
- Code Rigidity
Code that has dependencies in so many directions that you can’t make a change in isolation.
You change one part of the code and it breaks the calling/dependant class and you have to fix it there. In the end, because of that one change, you end up making changes in 10 different classes. - Code Fragility
When you make a change and an unrelated piece of code breaks. - Tight Coupling
It happens when a class depends on another class.
If you can relate to any of the above issues, then this article is for you!
In this article, we will learn how to overcome these issues using SOLID design principles.
So, Why do we need them?
We need them to write
- Flexible Code
- Maintainable Code
- Understandable Code
- Code can tolerate changes
What are SOLID principles?
SOLID is an acronym that stands for 5 design principles.
- S — Single Responsibility Principle (SRP)
- O — Open/Closed Principle (OCP)
- L — Liskov Substitution Principle (LSP)
- I — Interface Segregation Principle (ISP)
- D — Dependency Inversion Principle (DIP)
S — Single Responsibility Principle (SRP)
A module should have one, and only one reason to change.
What is a module?
The simplest definition is just a source file.
Some languages and development environments, though, don’t use source files to contain their code. In those cases, a module is just a cohesive set of functions and data structures.
Source: Clean Architecture, Robert C. Martin
Before understanding how SRP is followed/implemented/used, we should understand how it is not used.
Violation of SRP

Can you spot the violation?
The violation is that the Order
handles more than one responsibility which means it has more than one reason to change.
Solution

Create an Order
which is responsible for holding the order data.
Create OrderNotificationSender
which is responsible for sending notification updates to the user.
Create OrderInvoiceGenerator
which is responsible for generating the order invoice.
Create OrderRepository
which is responsible for storing the order in the database.
We have extracted different responsibilities from the Order
class into separate classes and each one of them has a single responsibility.
Optionally you can even go a step further and create a OrderFacade
which delegates the responsibilities to the individual classes.

As we can see that each class has a single responsibility, thus following the Single Responsibility Principle.
O — Open/Closed Principle ( OCP)
The OCP was coined in 1988 by Bertrand Meyer as
A software artifact should be open for extension but closed for modification.
In other words, the behavior of a software artifact ought to be extendible without having to modify that artifact.
Source: Clean Architecture, Robert C. Martin
Violation of OCP
To understand the violation of OCP, let’s take an example of a Notification Service that sends different types of notifications — Push Notifications and Email Notifications to the recipients.


Let’s say that I get a new requirement and we now support SMS Notifications, which means I have to update the Notification
enum and the NotificationService
to support SMS notifications.
So, the Notification
and NotificationService
will be like this

This means that every time we change the notification type, we will have to update the NotificationService
to support the change.
This is a clear violation of the OCP. Let’s see how you can abide by the OCP.
Solution

Create an interface Notification
.
Create the implementations of the Notification
of each type — PushNotification
, and EmailNotification
.
Create NotificationService
.
Now, your NotificationService
follows OCP as you can add/remove different types of notifications without modifying the NotificationService
.
Create SMSNotification
which implements Notification
.
As you can see, I have added SMSNotification
without modifying NotificationService
thus following the Open/Closed Principle.
Side Note:
This is the one principle that is really hard to follow and one can fully abide by it only in an ideal world.
As 100% closure is not attainable, the closure must be strategic.
L — Liskov Substitution Principle (LSP)
In 1988, Barbara Liskov wrote the following as a way of defining subtypes.
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
In other words, it means that the child type should be able to replace the parent without changing the behavior of the program.
Let us try to understand the principle by seeing the violation of the infamous Square/Rectangle problem.
Violation of LSP
We know that a rectangle is a polygon with 4 sides where opposite sides are equal and are at 90°.
A square can be defined as a special type of rectangle that has all sides of the same length.
If squares and rectangles were to follow LSP then we should be able to replace one with the other.
Please Note: The
Square
and theRectangle
are written in Java as Kotlin code would clearly show the violation without me proving it

Create a Rectangle
Create Square
Create Driver
to execute the flow.
In the above code, Driver
we can clearly see that Rectangle
and Square
cannot replace each other. Hence, LSP is clearly violated.
Under no circumstances the above problem will follow LSP. So, for the solution/example of the LSP, we will look at another problem.
Example of LSP
Let’s consider a Waste Management Service which processes different types of waste — Organic waste and Plastic waste.

Create Waste
interface
Create OrganicWaste
and PlasticWaste
which implements Waste
interface.
Create WasteManagementService
Create LSPDriver
In the LSPDriver
we can clearly see that we are able to replace different types of wastes, i.e Organic and Plastic, with each other without affecting the behavior of the program. Thus following the Liskov Substitution Principle.
I — Interface Segregation Principle (ISP)
The Interface Segregation Principle states that developers shouldn’t be forced to depend upon the interfaces that they don’t use.
In other words, the class that implements the interface shouldn’t be forced to use the methods it does not need.
Violation of ISP
Let’s assume that we are building a UI library that has components and the components can have different UI interactions like Click events - single-click and long-click.
We have an interface OnClickListener
that has different click behaviors, in order for a UI component to have this behavior, it must implement the OnClickListener
interface.

Create OnClickListener
Create CustomUIComponent
We can clearly see that the CustomUICompoenent
is forced to override onLongClick
method even though as per the requirements we don’t want the CustomUICompoenent
to have long click behavior.
This is a clear violation of the LSP.
Solution
This solution is straight-forward, we can separate the OnClickListener
interface into two different interfaces — OnClickListener
and OnLongClickListener
, they handle single-click behavior and long-click behavior respectively.

Create OnClickListener
Create OnLongClickListener
Create CustomUICompoenent
which implements OnClickListener
Now the CustomUIComponent
is not forced to override onLongClick
method. Hence following the Interface Segregation Principle.
D — Dependency Inversion Principle (DSP)
The Dependency Inversion Principle states that the most flexible systems are those in which code dependencies refer only to abstractions, not to concretions.
In order to understand this principle, you should know what I mean when I say that Class B
depends on Class A
.
Let’s go off-road for a bit to understand the above line.
Let’s say I have two classes ClassA
and ClassB
, the code is written as follows

You can see in line 9 that an object of ClassA
is created and in line 10 the method doSomething()
is called. As ClassB
needs an object of ClassA
to function properly, we can say that ClassB
depends on ClassA
.
With the help of DIP, we will inverse this dependency.

The above diagram shows DIP in action as we have inverted the dependency between ClassA
and ClassB
.
Now let’s see the example to understand DIP.
An example where the classes depend on each other
Let’s say we have a NotificationService
which sends only one type of notification, i.e email notifications, as it is tightly coupled with the EmailNotification
class.

Create EmailNotification
Create NotificationService
Create NotificationDriver
The problem is NotificationService
depends on EmailNotification
to send notifications. That is where the dependency comes in.
We have to remove the dependency in such a way that NotificationService
doesn’t depend on the type of notification and should be able to send different types of notifications.
Solution
The solution is pretty straightforward as we have already solved this problem when we looked at OCP.
In order for NotificationService
to be independent of the type of notification then it should depend on the abstract class or an interface rather than the concrete class, i.e EmailNotification
.

Create Notification
interface.
Create the type of notifications — EmailNotification
and SmsNotification
Create NotificationService
Create NotificationDriver
You can see that the NotificationService
now depends on the Notification
interface rather than the concretion, i.e Email Service, we can easily swap the implementation of the Notification
making the system flexible. Thus following the Dependency Inversion Principle.
Summary
All the SOLID principles can be defined in a single line as follows.
SRP — Each software module should have one, and only one, reason to change.
OCP — Software systems should be easy to change, they must be designed to allow the behavior of those systems to be changed by adding new code, rather than changing the existing code.
LSP — To build a software system from interchangeable parts, those parts must adhere to a contract that allows those parts to be substituted one for another.
ISP — Software designers should avoid depending on the things they don’t use.
DIP — The code that implements high-level policy should not depend on low-level details.
Source: Clean Architecture, Robert C. Martin
Conclusion
SOLID design principles are 5 core principles that every developer must know. These principles help us to write flexible, understandable, and maintainable code which is susceptible to changes.
How do you solve a similar problem in your project? Comment below or reach out to me on Twitter or LinkedIn.
Thank you very much for reading the article. Don’t forget to 👏 if you liked it.
References
- Clean Architecture by Robert C. Martin
- https://www.youtube.com/watch?v=zHiWqnTWsn4
- https://www.youtube.com/watch?v=yxf2spbpTSw
- https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898
— — Abhishek Saxena