- Last updated: 22 Jan, 2025
The SOLID principles are a set of five design principles that guide object-oriented programming and design. They help developers create systems that are easy to maintain, scale, and extend. These principles are particularly useful for writing clean and maintainable code in Kotlin. It was Conceptualized by Robert C. Martin, also known as Uncle Bob. Five Design principles of SOLID principles,
Single Responsibility Principle (SRP):
A class should have only one reason to change, meaning it should perform a single responsibility or task.
Example, Without SRP,
class UserManager {
fun addUser(user: User) {
// Add user logic
validateUser(user)
saveUserToDatabase(user)
sendWelcomeEmail(user)
}
private fun validateUser(user: User) {
// Validation logic
}
private fun saveUserToDatabase(user: User) {
// Database logic
}
private fun sendWelcomeEmail(user: User) {
// Email logic
}
}
With SRP,
class UserManager(private val userRepository: UserRepository, private val emailService: EmailService) {
fun addUser(user: User) {
userRepository.save(user)
emailService.sendWelcomeEmail(user)
}
}
class UserRepository {
fun save(user: User) {
// Save user to database logic
}
}
class EmailService {
fun sendWelcomeEmail(user: User) {
// Email sending logic
}
}
Here, the UserManager, UserValidator, and EmailService each have a single responsibility.
Open/Closed Principle (OCP):
Software entities (classes, modules, functions) should be open for extension but closed for modification.
Without OCP,
class PaymentService {
fun processPayment(paymentType: String) {
when (paymentType) {
"CREDIT_CARD" -> println("Processing credit card payment")
"PAYPAL" -> println("Processing PayPal payment")
}
}
}
interface PaymentProcessor {
fun processPayment()
}
class CreditCardProcessor : PaymentProcessor {
override fun processPayment() {
println("Processing credit card payment")
}
}
class PayPalProcessor : PaymentProcessor {
override fun processPayment() {
println("Processing PayPal payment")
}
}
class PaymentService(private val processors: List<PaymentProcessor>) {
fun processPayments() {
processors.forEach { it.processPayment() }
}
}
New payment types can be added without modifying PaymentService.
Liskov Substitution Principle (LSP):
Subtypes must be substitutable for their base types without altering the correctness of the program.
Without LSP,
open class Rectangle { var width: Int = 0 var height: Int = 0 open fun getArea(): Int { return width * height } } class Square : Rectangle() { override fun getArea(): Int { return width * width } }
With LSP,
interface Shape {
fun getArea(): Int
}
class Rectangle(private val width: Int, private val height: Int) : Shape {
override fun getArea(): Int {
return width * height
}
}
class Square(private val side: Int) : Shape {
override fun getArea(): Int {
return side * side
}
}
Here, Square and Rectangle both implement Shape without breaking substitutability.
Interface Segregation Principle (ISP):
Clients should not be forced to implement interfaces they do not use.
Without ISP,
interface Vehicle {
fun startEngine()
fun fly()
}
class Car : Vehicle {
override fun startEngine() {
println("Car engine started")
}
override fun fly() {
throw UnsupportedOperationException("Cars can't fly")
}
}
With ISP,
interface EngineVehicle {
fun startEngine()
}
interface FlyingVehicle {
fun fly()
}
class Car : EngineVehicle {
override fun startEngine() {
println("Car engine started")
}
}
class Airplane : EngineVehicle, FlyingVehicle {
override fun startEngine() {
println("Airplane engine started")
}
override fun fly() {
println("Airplane is flying")
}
}
Dependency Inversion Principle (DIP):
High-level modules should not depend on low-level modules; both should depend on abstractions.
Without DIP,
class UserService {
private val userRepository = UserRepository()
fun saveUser(user: User) {
userRepository.save(user)
}
}
class UserRepository {
fun save(user: User) {
// Save logic
}
}
With DIP,
interface UserRepository {
fun save(user: User)
}
class JpaUserRepository : UserRepository {
override fun save(user: User) {
// Save logic
}
}
class UserService(private val userRepository: UserRepository) {
fun saveUser(user: User) {
userRepository.save(user)
}
}
By depending on UserRepository, the UserService can work with any repository implementation.
By following these SOLID principles, you ensure your Java code is modular, reusable, and easier to maintain.
Similar Post
SOLID Principles Using Kotlin
- 22 Jan, 2025
- 3 min read
Master the SOLID principles to design robust, maintainable, and scalable software. Learn to create clean code by ensuring single responsibility, extensibility, substitutability, focused interfaces, and dependency management.
SOLID Principles Using Java
- 22 Jan, 2025
- 3 min read
Master the SOLID principles to design robust, maintainable, and scalable software. Learn to create clean code by ensuring single responsibility, extensibility, substitutability, focused interfaces, and dependency management.