# SOLID Design Principles In Kotlin

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675332936759/87440303-9153-4e8a-b0e4-e3571751a7be.png align="center")

In this article, we will figure out what exactly **SOLID principles** are and, most importantly, we will check out a few **Kotlin examples** to get an even better understanding. SOLID is an acronym for five design principles in Object-Oriented software development intended to make software designs more understandable, flexible and maintainable. These five principles are described by Robert C. Martin.

SOLID principles are briefly as follows:

* **S** — Single Responsibility Principle (known as SRP)
    
* **O** — Open/Closed Principle
    
* **L** — Liskov’s Substitution Principle
    
* **I** — Interface Segregation Principle
    
* **D** — Dependency Inversion Principle
    

## **1\. Single-Responsibility Principle**

The single-responsibility principle states:

> *A class should have only one reason to change.*

The Single Responsibility Principle indicates that every class should have one and only one responsibility. In other words, if our class does more than one responsibility, we should split the functionalities and responsibilities in various classes. Therefore, this makes the class more robust.

With all of that in mind, let’s see the following example:

```kotlin
class User(
  val id: Int,
  val name: String,
  val email: String,
  val phoneNumber: String
)
class AdminDashboardService {
  fun sendNotification(user: User) {
    println("Preparing email content")
    println("Sending email to ${user.email}")
  }
  fun deleteUser(user: User) {
    println("Deleting user with id ${user.id} from the database")
  }
}
```

For the purpose of simplicity, the above functions are just printing some text to the output. Nevertheless, in the real-life scenarios the *sendNotification()* would be responsible for preparing an HTML content for the email and sending it to the given email address. On the other hand, the *deleteUser()* would perform an SQL query deleting the record from connected database.

In such a case, we can clearly see that our service is responsible for **three different things**. Moreover, let’s imagine that:

* the marketing team requested a change in the e-mail template because of the branding change
    
* the CTO requested an email automation provider change
    
* the data team requested a change in SQL query
    

We can clearly see that each of these requests may easily affect theoretically unrelated business functions.

As the next step, let’s see how the refactored code could look like:

```kotlin
class UserAccountService {
  fun deleteUser(user: User) {
    println("Deleting user with id ${user.id} from the database")
  }
}
class EmailContentProvider {
  fun prepareContent() {
    println("Preparing email content")
  }
}
class EmailNotificationService {
  fun sendNotification(user: User) {
     println("Sending email to ${user.email}")
  }
}
```

This time, we can clearly see that each class is responsible for exactly one thing (has one reason to change).

**Benefits**

* most importantly- any bug introduced to the particular class **affects less parts of the system** (and organization as a whole)
    
* additionally, the number of **merge conflicts** is reduced when multiple people are working with the codebase
    
* whatsoever, it can introduce much better **readability** than the monolithic classes
    

# **2\. O — Open/Closed Principle**

As the next step, let’s take a look at the **open-closed principle:**

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

The Open/Closed principle states “software entities such as classes, components, and modules should be open for extension but closed for modification." That is, such an entity can allow its behavior to be extended without modifying its source code.

Let’s imagine that we were asked to implement a logging feature for custom header objects. However, the logger itself should avoid printing the values of particular headers: *header-one* and *header-two,* as they contain vulnerable data.

Given these requirements, we’ve decided to put the excluded headers inside the companion object and expose a *log()* function, which will make sure that we won’t log any vulnerable data:

```kotlin
data class Header(
  val name: String,
  val value: String
)
class HeadersLogger {
  companion object {
    private val forbiddenHeaders = setOf("header-one", "header-two")
  }
  fun log(header: Header) {
    if (!forbiddenHeaders.contains(header.name))
      println(header.value)
  }
}
```

Everything works perfectly, but what if at some point the client asks us to exclude additional headers?

Given the above code, we have no other option, than to either modify the *HeadersLogger* class to contain the additional header or implement a new one. Definitely, the above code is **neither open for extension nor closed for modification**.

How could we change the above code to follow the above principle? Well, let’s see the following snippet:

```kotlin
class HeadersLogger(val forbiddenHeaders: Set<String> = setOf()) {
  companion object {
    private val forbiddenHeaders = setOf("header-one", "header-two")
  }
  fun log(header: Header) {
    val headerName = header.name
      if (!forbiddenHeaders.contains(headerName)
        && !additionalForbiddenHeaders.contains(headerName)
      )
        println(header.value)
  }
}
```

As we can see, such a small change gives us much more flexibility. Although we’ve predefined two headers, which should be always skipped, we can easily add new without modification of the existing class. Furthermore, we can extract this code as a common dependency and reuse it in multiple parts of the system.

**Benefits**

* first of all, **reusability** and **flexibility**. We can use already existing codebase to implement new features or apply changes without the need of reinventing the wheel
    
* moreover, the above advantage is a great **time-saver**
    
* additionally, modification of existing classes might introduce unwanted behavior everywhere they’ve been used. With the open-closed principle, we can easily **avoid this risk**
    

# **3\. L**iskov substitution principle

Next, let’s check the definition of the **Liskov substitution principle:**

> *Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.*

This principle indicates that parent classes should be easily substituted with their child classes without changing the behavior of software. It means that a sub-class should override the methods from a parent class, which does not break the functionality of the parent class.

Let’s say that we’ve got two types of users in the app: standard and admin. Both types of the account can be created. Nevertheless, the admin account can not be deleted in our app (for instance it can be done only from an external one).

Given that, let’s have a look at the example:

```kotlin
interface Managable{
  fun create()
  fun delete()
}
class StandardUser : Managable{
  override fun create() {
    println("Creating Standard User account")
  }
  override fun delete() {
    println("Deleting Standard User account")
  }
}
class AdminUser : Managable{
  override fun create() {
    println("Creating Admin User account")
  }
  override fun delete() {
    throw RuntimeException("Admin Account can not be deleted!")
  }
}
```

At first glance, everything seems to be working, as expected. The interface methods has been overriden and we can compile it successfully. Additionally, we’ve applied the requirements and the *delete()* implementation prohibits from deleting the Admin account.

Nevertheless, we can clearly spot, that **replacement of the interface invocation with the derived method will definitely break the flow**. Let’s imagine that someone else wrote a generic test for Managable interface. Given the contract, tester assumed that invoking the *delete()* should remove the data from the external database regardless of the user type. Unfortunately, that’s not the case here and the test will work as expected with the StandardUser instance and fail with the exception for the AdminUser.

There are plenty of possibilities when it comes to the above code. Let’s see the example one:

```kotlin
interface Creatable {
  fun create()
}
interface Deletable {
  fun delete()
}
class StandardUser : Creatable, Deletable {
  override fun create() {
    println("Creating Standard User account")
  }
  override fun delete() {
    println("Deleting Standard User account")
  }
}
class AdminUser : Creatable {
  override fun create() {
    println("Creating Admin User account")
  }
}
```

This time, we’ve introduced a more specific contract with two, separate interfaces. Definitely, the hypothetical substitution **won’t break the flow**.

**Benefits**

* when our subtypes conform behaviorally to the supertypes in our code, our **code hierarchy becomes cleaner**
    
* furthermore, people working with the abstraction (interface in our case) can be sure that **no unexpected behavior occurs**
    

# **4\. I**nterface segregation principle

We've already covered three rules, so it’s time to check the **interface segregation principle:**

> *Many client-specific interfaces are better than one general-purpose interface.*

The interface-segregation principle indicates classes that implement interfaces, should not be forced to implement methods they do not use. This principle is related to the fact that a lot of specific interfaces are better than one general-purpose interface. In addition, this is the first principle, which is used in interface, all the previous principles applies on classes.

You might already noticed that we’ve already seen this violation in the chapter five:

```kotlin
interface Managable{
  fun create()
  fun delete()
}
```

First of all, people working with the above interface can not be sure what exactly are its boundaries at first glance. What exactly does "manage" mean in terms of our application? Is it just creating and deleting people, or should suspending also count in this case?

Furthermore, frequent violations of this rule may lead to the creation of a monolithic monster. And trust me, we don’t want to get back to such a code at some point in the future.

Similarly, we’ve already introduced a possible solution in the previous chapter:

```kotlin
interface Creatable {
  fun create()
}
interface Deletable {
  fun delete()
}
```

This time, we don’t have any doubts when working with the above interfaces. We can definitely assume that all classes implementing *Creatable* will be responsible for the creation, and in case of *Deletable-* for removal.

**Benefits**

* well designed interfaces help us to **follow the other principles**. It’s much easier to take care of single responsibility and as we could see- Liskov substitution
    
* additionally, precise contract described by the interface makes the code **less error-prone**
    
* whatsoever, it really **improves readability** of the hierarchy and the codebase itself
    

# **5\. D**ependency inversion principle

Finally, let’s check out the **dependency inversion principle:**

> *Depend upon abstractions, \[not\] concretions.*

This principle suggests that high level modules should not depend on low level. So, both should depend on abstractions. Furthermore, abstraction should not depend on details. Details should depend upon abstractions.

Just like in the previous examples, let’s see the code, which does not apply the above principle:

```kotlin
class EmailNotificationService {
  fun sendEmail(message: String) {
    val formattedMessage = message.uppercase()
    println("Sending formatted message: $formattedMessage")
  }
}
```

As we can see, the *EmailNotificationService* will send a formatted to upper case message. Although everything is working as expected, we can spot that this method depends on the specific implementation.

Let’s modify the service a bit:

```kotlin
class EmailNotificationService {
  fun sendEmail(message: String, formatter: (String) -> String) {
    val formattedMessage = formatter.invoke(message)
    println("Sending formatted message: $formattedMessage")
  }
}
```

This time, we made the *EmailNotificationService* independent of the formatter implementation. The only thing that this service care about is that the formatter has to return a String value. As we can see, applying this principle gives us much more flexibility.

**Benefits**

* allows the codebase to be easily expanded and extended with new functionalities
    
* furthermore, it improves reusability
    

# **Conclusion**

Writing code according to SOLID principles will make our software more readable, understandable, flexible, and maintainable.

@[Mansi Vaghela](@mansivaghela) [**LinkedIn**](https://www.linkedin.com/in/mansi-droid/) [**Twitter**](https://twitter.com/mansi_droid) [**Github**](https://github.com/mansi-droid) [**Youtube**](https://www.youtube.com/channel/UCoGcTjvUp32To72hQVe7iZw)
