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:
Single Responsibility Principle (SRP)
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
}
}
Open/Closed Principle (OCP)
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.
Liskov Substitution Principle (LSP)
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
`Interface Segregation Principle (ISP)
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.
Dependency Inversion Principle (DIP)
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
Top comments (0)