SOLID Design Principles In Kotlin

Abhishek Saxena
ProAndroidDev
Published in
9 min readDec 5, 2022

--

Photo by Med Badr Chemmaoui on Unsplash

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 the Rectangle 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 NotificationServicedepends 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

— — Abhishek Saxena

--

--

I am a Mobile Full Stack Developer with 2 years of experience with expertise in Native Android and Spring Boot. https://github.com/5AbhishekSaxena