Android Solid priciples in koltin
Published: 2023-04-30 21:47:59

SOLID is an acronym for five principles of Object-Oriented Design:

Single Responsibility Principle (SRP)
Open/Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
Let's take a look at each principle with code examples:

<u>Single Responsibility Principle (SRP)
</u>The SRP states that a class should have only one reason to change. In other words, a class should have only one responsibility. Here's an example:

```
kotlin
// Bad example - violating SRP
class FileManager {
    fun readData() {
        // read data from file
    }
    fun writeData() {
        // write data to file
    }
    fun processData() {
        // process data
    }
}
```
In this example, the FileManager class has three responsibilities - reading data, writing data, and processing data. This violates the SRP. A better approach would be to create three separate classes, each with a single responsibility:

```
kotlin
// Good example - following SRP
class DataReader {
    fun readData() {
        // read data from file
    }
}

class DataWriter {
    fun writeData() {
        // write data to file
    }
}

class DataProcessor {
    fun processData() {
        // process data
    }
}
```

<u>Open/Closed Principle (OCP)
</u>The OCP states that a class should be open for extension but closed for modification. In other words, you should be able to extend the behavior of a class without modifying its code. Here's an example:

```
kotlin
// Bad example - violating OCP
class PaymentProcessor {
    fun processPayment(payment: Payment) {
        if (payment.type == "credit card") {
            // process credit card payment
        } else if (payment.type == "paypal") {
            // process paypal payment
        } else {
            throw IllegalArgumentException("Invalid payment type")
        }
    }
}
```
In this example, the PaymentProcessor class has a method that processes payments based on their type. If a new payment type is added, the code of the PaymentProcessor class needs to be modified. A better approach would be to use polymorphism to extend the behavior of the class:

```
kotlin
// Good example - following OCP
interface PaymentMethod {
    fun processPayment(payment: Payment)
}

class CreditCardPayment : PaymentMethod {
    override fun processPayment(payment: Payment) {
        // process credit card payment
    }
}

class PaypalPayment : PaymentMethod {
    override fun processPayment(payment: Payment) {
        // process paypal payment
    }
}

class PaymentProcessor(private val paymentMethod: PaymentMethod) {
    fun processPayment(payment: Payment) {
        paymentMethod.processPayment(payment)
    }
}
```
In this example, we use the PaymentMethod interface to define the behavior of payment methods. We then create two classes that implement this interface - CreditCardPayment and PaypalPayment. Finally, we modify the PaymentProcessor class to use the PaymentMethod interface. This way, we can add new payment methods without modifying the PaymentProcessor class.

<u>Liskov Substitution Principle (LSP)
</u>The LSP states that a subclass should be substitutable for its superclass without changing the correctness of the program. In other words, a subclass should be able to replace its superclass without affecting the behavior of the program. Here's an example:

`// Base class
open class Vehicle {
    open fun accelerate() {
        // accelerate
    }
}

// Derived classes
class Car : Vehicle() {
    override fun accelerate() {
        // accelerate car
    }
}

class Bicycle : Vehicle() {
    override fun accelerate() {
        // accelerate bicycle
    }
}

// Function using the base class
fun race(vehicle: Vehicle) {
    vehicle.accelerate()
}

// Usage
val car = Car()
val bicycle = Bicycle()

race(car) // okay
race(bicycle) // okay
<u>`Interface Segregation Principle (ISP)
</u>The ISP states that a client should not be forced to implement interfaces they don't use. In other words, interfaces should be designed in a way that clients only need to implement the methods they actually use. Here's an example:

```
kotlin
// Bad example - violating ISP
interface User {
    fun register()
    fun login()
    fun logout()
    fun deleteAccount()
}

class RegularUser : User {
    override fun register() { /* implementation */ }
    override fun login() { /* implementation */ }
    override fun logout() { /* implementation */ }
    override fun deleteAccount() { /* implementation */ }
}

class AdminUser : User {
    override fun register() { /* implementation */ }
    override fun login() { /* implementation */ }
    override fun logout() { /* implementation */ }
    override fun deleteAccount() { /* implementation */ }
}
```
In this example, the User interface has four methods - register, login, logout, and deleteAccount. However, the AdminUser class does not need to implement the register method, since admins are not allowed to register. This violates the ISP. A better approach would be to create separate interfaces for each responsibility:

```
kotlin
// Good example - following ISP
interface Registerable {
    fun register()
}

interface Loginable {
    fun login()
    fun logout()
}

interface DeletableAccount {
    fun deleteAccount()
}

class RegularUser : Registerable, Loginable, DeletableAccount {
    override fun register() { /* implementation */ }
    override fun login() { /* implementation */ }
    override fun logout() { /* implementation */ }
    override fun deleteAccount() { /* implementation */ }
}

class AdminUser : Loginable, DeletableAccount {
    override fun login() { /* implementation */ }
    override fun logout() { /* implementation */ }
    override fun deleteAccount() { /* implementation */ }
}

```
In this example, we create separate interfaces for each responsibility - Registerable, Loginable, and DeletableAccount. We then modify the RegularUser and AdminUser classes to implement only the interfaces they need.

<u>Dependency Inversion Principle (DIP)
</u>The DIP states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. In other words, classes should depend on interfaces, not implementations. Here's an example:

```
kotlin
Copy code
// Bad example - violating DIP
class UserService(private val userRepository: UserRepository) {
    fun getUserById(userId: Int): User {
        return userRepository.getUserById(userId)
    }
}

class UserRepository {
    fun getUserById(userId: Int): User {
        // get user from database
    }
}
```
In this example, the UserService class depends on the UserRepository class, which is a low-level module. This violates the DIP. A better approach would be to introduce an abstraction:

```
kotlin
// Good example - following DIP
interface UserRepository {
    fun getUserById(userId: Int): User
}

class UserService(private val userRepository: UserRepository) {
    fun getUserById(userId: Int): User {
        return userRepository.getUserById(userId)
    }
}

class DatabaseUserRepository : UserRepository {
    override fun getUserById(userId: Int): User {
        // get user from database
    }
}
```
In this example, we introduce the UserRepository interface, which is an abstraction. We then modify the UserService class to depend on the UserRepository interface, not the UserRepository class. Finally, we create a new `Database