Five SOLID principles in Android

Sana Ebadi
12 min readOct 8, 2020

The term SOLID expresses 5 important principles for object-oriented design, the complete form of which will be as follows:

Single Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle

Background in SOLID:

SOLID was introduced in the early 2000s by Robert Martin (Uncle Bob) and was coined by Michael Feathers. When these five principles of object-oriented design are put together, they help developers facilitate the development of sustainable systems.

Note: I have prepared a project for this article on the Github site, which you can refer to better understand the continuation of the post!

The First Principle: The Single Responsibility Principle

The Single Responsibility principle abbreviated SRP, states the following:

A class should have only one task (a class should have only one reason to change, no more)!

For example, let’s talk a little about the RecyclerView adapter. What is the function of Adapter for Recycler View ?! Convert data to the correct format for UI display.
The most practical part of the Recycle Reactor adapter class is the onBindViewHolder method, which must convert a set of data to the required format.
This means that if you are going to take on another task with this adapter class, we have violated the SRP principle.

Example of SRP:

Suppose this is our class of recycleView adapter:

In the above code, if you pay attention to the onBindViewHolder method, you will notice that in addition to its task, it also executes other commands:

As we mentioned, this method only needs to handle data, but it does not work as a single task here! And this violates the first principle of solidity.
What is the problem ?!

Incorporating multiple responsibilities into one class can create many problems. First, the order calculation logic now comes with the adapter. If you need to display the whole order elsewhere (most likely to do so), you need to repeat that logic. After this, your program is exposed to repetitive software logic issues that we are all familiar with. You update the code in one place and forget to update it in another place and so on.

You have connected the formatting logic to the adapter. What if there is a need to relocate or upgrade? The program is more prone to bugs due to excessive responsibility in one place.

Fortunately, this simple example can be easily solved by extracting the calculation of the entire order into the order object and converting them into a currency format as a form of currency formatting. This template can also be used to order.

For Refactoring, we do this method, that is, we remove the currency conversion operation from this method:

Here is a quote from Uncle Bob:

In the context of the Single Responsibility Principle (SRP), we define responsibility as “a reason for change”. If you can think of more than one motive for changing a class, then that class has more than one responsibility.

The challenge is to know when to use SRP and when not. According to the adapter example, if we look at the code again, we see different things that can change in different areas for different reasons:

And be sure to have two datasets, Order and LineItem, as separate classes.

Conclusion :

A class should have only one reason to change.

To this end, it is also one of the most difficult actions. Over-analyzing the code can make you think you have to follow the SRP, but be aware that overuse and misuse can complicate the code. My advice is to try to take a step away from the code and look at it objectively. If you do this, you will likely see different things about your code that you may not be aware of. You may find that you need to apply a single-task model.
Finally, as you change your schedule, you may find that you may need to apply SRP to areas you did not need before. This is quite good and recommended.

The Second Principle: The Open / Closed Principle

The second principle of the five solid principles, which refers to the letter O, is called OCP for short.

The principle of open-closed; Software components must be open to development (That is, it is receptive to development) and closed to correction (That is, it is not receptive to correction). For example, to add a new feature to the software, you do not need to rewrite some parts of the code, but you can easily add that feature to the software like a plugin.

Example for OCP:

The following example is an example of the Open / Closed principle.

Suppose you have a program that needs to calculate the area for each particular shape. The program must be able to calculate the given area of ​​all products for an insurance policy.

In this example, we abstract the area calculation into a class called AreaManager. The AreaManager class has a single responsibility — to calculate the total area of ​​available shapes.

Let’s assume that we are currently only working with rectangular products so we have a rectangular class that shows this. What it looks like in these classes:

Rectangle.java

AreaManager.java

So far everything looks good until we are going to send another figure to the AreaManager class to calculate the area. We will have the Circle class as follows:

Circle.java

So we need to edit the Area Manager class as follows:

This code has a problem!

If we were to add another geometric shape such as a triangle to this project, we would have to change the AreaManager class.

This class violates the Open / Closed principle. It is not closed for reform and is not open to development. We have to modify AreaManager every time a new form is created. We want to prevent this.

For Refactoring this code, we do this:

Since AreaManager is responsible for calculating the area of all shapes, and since area calculation for each shape is unique, it only makes sense to move the area calculation for each shape to the corresponding class.

But this makes AreaManager aware of all bug classes, right ?! So the best way is to use interfaces.
We will have an interface called Shape:

Shape.java

And other classes use this interface like this:

Then I write the AreaManager class with the help of OCP like this:

We have made changes in AreaManager that allow it to be closed for modification but open for expansion. If we need a new shape like an octagon, AreaManager will no longer need to be changed because it is open for expansion via the Shape interface.

Conclusion :

hat is Open / Close. With a little planning and abstraction, you can create code that is easy to maintain and develop, and does not need to be modified to add new functionality.

The Third Principle: The Liskov Substitution Principle

The third letter of SOLID is the expression L, which is the principle for Liskov Substitution. The principle of Lyskov’s succession was introduced by Barbara Liskov in 1987 in a keynote address at a conference. The principle of Lyskov’s replacement states the following:

Objects in a program that belongs to a parent class should be easily replaceable with the parent class without the need to change the program.

Example for LSP:

Java is a static topic language. The compiler notifies errors that occur. You try to convert a string to Long or vice versa, and the compiler tells you that you made a mistake. The compiler also allows us to write code that follows the principle of Lyskov substitution.

Suppose you are writing Android code that allows you to work with the List type in Java:

Suppose we have a code like this:

In the example code above, the customer repository needs a list of customers to be able to get those customers. The client repository only needs to have that list of customer IDs from the <Integer> type list. When we call the customer repository, we present the ArrayList <Integer> like it:

Wait a minute … customer repository needs List <Integer> not ArrayList <Integer>! So how does it still work ?!

This is the principle of replacing Lyskov. Since ArrayList <Integer> is a subset of List <Integer>, it will not be a problem; We replace the requested sample (List <Integer>) with an instance of its subgroup (ArrayList <Integer>).

In other words, in the above code, we depend on abstraction (List <Integer>), so we can create a subgroup (ArrayList <Integer>) and the program still runs smoothly. Why is that?

This is because the customer repository depends on the contract provided by the List interface. ArrayList is the execution of the List interface, so when the program is executed, the customer repository does not see what the ArrayList type is but uses it as an instance of the List. The main part of the LSP Wikipedia article explains this very well, so I want to quote it here:

Liskov’s notion of a behavioral subtype defines a notion of substitutability for mutable […] objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).

In short, we can replace anything that expands the list and not crash the program.

I’m sure you have used the same code many times in your program! That is, you followed the Lyskov principle.

Conclusion :

The principle of replacing Lyskov is very simple. You use it every day and it brings you great benefits as a developer. You can do this not only with List but also with your personal interfaces.

The Fourth Principle: The Interface Segregation Principle

The principle of interface Segregation; Using multiple interfaces, each with only one task, is better than using a multifunction interface.

It is very easy to understand. Let’s start with an example we’re all familiar with on Android — View on Android.

As you know, the Android View class is the main superclass for all Android views. View root and base TextView, Button, LinearLayout, CheckBox, Switch, and so on.

Now let’s assume that you are one of the programmers who rewrote the Android operating system in the mid-late 2000’s. You know that most likely every component should be clicked. So, as a good Java programmer, you create an interface called OnClickListener that sits in the View class and looks like this:

After a while you will need a long click for the component so the above interface is rewritten as follows:

As needed, you will need to touch the screen by the user, so after rewriting, you will be faced with this code:

At this point, you decide to rename this interface to ViewInteractions or something like that ?! Because by adding the last method, this interface is out of click mode.

Using the same OnClickListener from above, imagine that we built an application using the Android SDK.
I want to write a click listener for a button:

The last two functions, onLongClick and onTouch, do nothing. Sure, we can put some code in there, but what if we don’t need it?

The user interface is highly dependent because it requires the client to implement all the methods, even if they do not need them. Let’s review the ISP short definition again:

Make fine grained interfaces that are client-specific.

By doing this, you will not have a clean interface as it would be problematic to use two functions without the need for them. In the example above, the user does not need onLongClick and onTouch.

For example, TextView has a addTextChangedListener method. The TextWatcher interface offers three methods:

Conclusion :

The principle of interface separation helps you maintain and update the project.

Imagine you have a very large interface. And if you need to change, now you have to change it. But with the help of the ISP principle, code development and maintenance become easier for you.

The Fifth Principle: Dependency Inversion Principle

The principle of dependence inversion; It is better for the program to depend on abstraction rather than implementation.

In this section, we will get acquainted with the last SOLID rule, namely Dependency Inversion. When you write programs based on object-oriented techniques, you will definitely have classes that are dependent on other classes. Remember the SRP rule? We said that each class should do only one specific task and delegate the other tasks to the respective classes. But there should be no direct connection between classes! It is said that the connection between classes should be Loosely Coupled. Consider the following example:

We have a class called DatabaseManager, which calls each of its methods and sends an email to a specific address. In the above code, the tasks are divided, ie the SRP rule, but the connection between the DatabaseManager class and the EmailNotification class is direct. Suppose we want to use SMS instead of sending an event via email, a new class must be defined and the DatabaseManager class changed to allow events to be sent via SMS. But with the implementation based on the Dependency Inversion rule, this will be easily possible, to do this we first define an interface called INotification:

Now, the class of each class that performs the event submission operation must implement the interface we defined, we define the following two classes: EmailNotification and SMSNotification:

Now change the DatbaseManager class so that its dependence on a class is eliminated and the interface dependency is defined:

With the above change, the DatabaseManager class has no dependence on a specific class, and it is possible to specify the corresponding Dependency for the object when it was created:

And if we want to use the email service:

With the changes made, we applied the DIP rule to our code.

Here we conclude this article on the five principles of SOLID.

Thanks for reading! And if you enjoyed it, gimme a clap

Sana Ebadi | 20:11 PM Thursday, 8 October 2020

--

--