SOLID Principles for Android
Kumar Rishi
Posted on June 9, 2024
As a developer, there comes a time when we have to learn principles for moving ahead in career. For some this topic might be new, for some they are already experienced on these principles. I will try to make sure, this article may come in help for both of them.
So Let's Begin,
Every letter in SOLID , represents one principle.
The Fist Principle that we will talk about
S - Single Responsibility Principle ( SRP )
This one is easy to understand, but it might take you some time to get in memory muscle.
Real world Example - Let's say you are a chef in a restaurant, now you should be responsible for making the food right.
Now if you given also the task of handling the staffs in kitchen or serving food, will make the work complicated right.
That's how in programming, a method that you have declared should be assigned with only one responsibility and that is the only reason it should change. In this manner it will make code more convincible, workable and readable.
For ex :
class Chef{
fun cook(){
println("Cooking")
}
fun orderIngredients(){
println("Ordering Ingredients")
}
fun manageStaff(){
println("Managing Staff")
}
}
fun main(){
val chef = Chef()
chef.cook()
chef.orderIngredients()
chef.manageStaff()
}
// but
// chef should only cook
class Chef1{
fun cook(){
println("Cooking")
}
}
class Manager{
fun orderIngredients(){
println("Ordering Ingredients")
}
fun manageStaff(){
println("Managing Staff")
}
}
let's take an example of common usecase, when we are handling the authentication for our app,
class LoginMethod(
private val firebaseAuth: FirebaseAuth
) {
fun signIn(email: String, password: String) {
// Input validation
if (email.isEmpty() || password.isEmpty()) {
throw IllegalArgumentException("Invalid input")
}
// Authentication
try {
firebaseAuth.signInWithEmailAndPassword(email, password)
println("Login successful")
} catch (e: Exception) {
// Error handling
println("Login failed: ${e.message}")
}
}
}
Here we are making this signIn function to handle more responsibility than it should do, like validating the fields and handling errors also
Here's how you can fix it,
class LoginMethod(
private val firebaseAuth: FirebaseAuth,
private val fieldValidator: FieldValidator,
private val errorHandler: ErrorHandler
) {
fun signIn(email: String, password: String) {
// Input validation
if(!fieldValidator.validateEmail(email) || !fieldValidator.validatePassword(password)) {
throw IllegalArgumentException("Invalid Format")
}
// Authentication
try {
firebaseAuth.signInWithEmailAndPassword(email, password)
println("Login successful")
} catch (e: Exception) {
// Error handling
errorHandler.handleError(e)
}
}
}
class FieldValidator {
fun validateEmail(email: String): Boolean {
return email.contains("@")
}
fun validatePassword(password: String): Boolean {
return password.length >= 6
}
}
class ErrorHandler {
fun handleError(e: Exception) {
println("An error occurred: ${e.message}")
}
}
Here our signIn function can only be responsible for login , not for checking validation or logging error.
Open/ Closed Principle ( OCP )
Here this principle states that, a class should open for extension but closed for modification.
Let's take an example
Real world example , say there is a car and it has music system which can play music via usb.
Now say, music system nowadays are coming with bluetooth connectivity also, then the engineers will not open each part of this music system, and install bluetooth module and close it, right. For one time it should feel convenient but for large case definitely not.
Instead they can extend one common module, where any new modules like BLE, WiFi can be connected, in this way they don't have to modify the system every time.
Let's understand with programming,
open class Shape {
open fun draw() {
println("Drawing Shape")
}
}
Here if we want to change this class for every different shape, then it might change the code for the whole app, wherever you are using this.
Here intention is we should not modify how the class behaves or works.
so in this case , the class Shape should not be modified to accomodate new types of shapes
open class Shape {
open fun draw() {
println("Drawing Shape")
}
open fun drawSquare() {
println("Drawing Shape")
}
open fun drawRect() {
println("Drawing Shape")
}
}
Instead you can modify this as
open class Shape {
open fun draw() {
println("Drawing Shape")
}
}
class Square : Shape() {
override fun draw() {
println("Drawing Square")
}
}
class Circle : Shape() {
override fun draw() {
println("Drawing Circle")
}
}
Will post other principles very soon, till then if you have any suggestion, i'm open to any discussion.
Posted on June 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.