S.O.L.I.D Principles in Android Development

Akshay Kumar Aggarwal
Powerplay
Published in
5 min readDec 3, 2021

--

In software development change is the only constant. We constantly need to cater to new requirements and add new features. In this ever changing software development there are some principles which makes our life easy by making our code more readable, extensible, and maintainable. These principles are called SOLID design principles.

In the last 20 years, these five principles have changed the way that we write software in object-oriented programming. They provide high-level guidelines to design better software applications. Yet they do not provide implementation guidelines and are not bound to any programming language.

In this article, we’ll go through how SOLID principles are relevant and used in the context of Android Development

SOLID is a mnemonic acronym for these five principles:

  1. Single Responsibility
  2. Open/Closed
  3. Liskov Substitution
  4. Interface Segregation
  5. Dependency Inversion

While these concepts may seem daunting, they can be easily understood with some simple code examples. We will delve into these principles in the following sections, with a short Kotlin example to illustrate each one.

Single Responsibility principle (SRP)

It simply states that a class should only have one responsibility. Which means it should only have one reason to change.

An object should not know too much about anything. Let’s understand this by using an example. Suppose you have a car class.

Here in the above example class car has more than one responsibility. Car’s name is justified as it is a property of the car. But saving and retreiving car from DB gives car class a reason to change other than car class own properties.
More SRP valid code would be like:

SRP makes our code more cohesive and decoupled.

Open/Closed Principle (OCP)

This principle states that classes should be open for extension but closed for modification. It simply means we should avoid changing the existing code and try to extend classes for newer functionalities.

Let us try to understand this better with a similar car example. Now we try to count the number of doors in the car.

Function printCarDoorCount() violates the opened-closed principle. Because it cannot be closed against a new kind of cars! Adding new cars requires more if-else statements. More conditions could lead to unexpected errors if we miss one.

A better approach to this would be: Define Car as an abstract class that has abstract functions like doorsCount. If you want to make a new car, you just need to extend this class and override the abstract functions suitable to your need. This way you can remove dependency on if-else conditions. Code looks more concise, is more readable and easily extendable to newer cars.

Liskov Substitution Principle (LSP)

This principle states that a sub-class must be substitutable for its super-class. It simply means Parent class should be able to be replaced by any of its Subclasses anywhere in the code without disrupting the behaviour of our code.

Let’s understand this more with our car example. We will write a function that will print the Car’s engine capacity:

The function printEngineCapacity will print the engine capacity for each car. This is against both Open Closed Principle and Liskov Substitution Principle. The printEngineCapacity() function will not work as expected if we add a new Car type and send it with the array. We must add a new else-if to make it work, which is again a bad code. So how can we make it better code? Here comes LSP to the rescue. Remember we can substitute parent class with its child classes.

The printEngineCapacity() method no longer requires knowledge of the Car type to determine the Engine Capacity. Instead, it simply calls the Car type’s engineCapacity() function because a sub-class of the Car class is required to implement the engineCapacity() function(as determined in the abstract Car class).

Interface Segregation Principle (ISP)

This principle states that Clients should not be forced to depend upon interfaces that they do not use. It simply means many client-specific interfaces are better than one general interface (Where users will be forced to implement methods that will not be used).

The interface segregation principle addresses the drawbacks of implementing large interfaces.

Let’s try to understand this from the sports example below:

Here ISport interface knows far more than it requires. getReady() and stop() are shared methods across all Sport classes. We added extra unused functions run(), longJump(), and highJump() to the Swimming class.

If we add another sport that extends from ISport, such as Running, we will add three more unused methods (swim(), longJump(), and highJump()). So, let’s look at a better way to do it.

Now, ISport contains only the methods that need to be implemented in all inherited classes. The ISwimming interface contains only swim() which needs to be implemented for swimming.
You can also add a start() method to the ISport interface to implement the start() method in any class.

Dependency Inversion Principle (DIP)

This principle states that High-level modules should not depend on low-level modules. Both should depend on abstractions.

In other words, Abstractions should not depend on details. Details should depend on abstractions.

It simply means Dependency should be on abstractions, not concretions.

In above example dependency is on concrete objects rather than abstractions.

A better way would be:

When classes are dependent on abstractions it is very easy to extend the functionality for new use cases. Like in above example if we need to add new type of worker say DailyWorker, we just need to implement IWorker interface to this DailyWorker class. No change is required in the IManager or Manager class.

Here both high-level and low-level modules rely on abstractions. Of course, this comes with the cost of writing additional code, but the benefits of DIP outweigh the additional costs.

This principle does not apply to all classes and modules. For example, if you have a class with features that may not change in the future, you do not need to apply this principle.

Conclusion

Fundamentally SOLID Design principles provide high-level guidelines to design better software applications. Using SOLID principles in Android development could be helpful to follow clean code principles. Without using structured design principles such as SOLID principles can create long-lasting problems, and probability of success for an application will be decreased in future.

Thank you for reading, hope it helps ✨ Any doubts or suggestions in the comment section are most welcome.

Examples are inspired from this blog post

Follow me on Linkedin | Twitter | Github

--

--