The SOLID Principles for Android Developers

Kayvan Kaseb
Software Development
6 min readMar 8, 2020
The SOLID Principles for Android Developers

Basically, SOLID is one of the most significant acronyms in the Object Oriented Programming concepts. Using SOLID principles in Android development could be helpful and effective to follow clean code principles. Therefore, if Android developers design and implement their codes without using structured design principles such as SOLID principles, they will create long-lasting problems, and probability of success for an application will be decreased in future. This essay aims to discuss the importance of SOLID principles for Android developers in designing and implementing a robust application.

What is the definition of SOLID?

SOLID is an acronym for five design principles in Object-Oriented software development intended to make software designs more understandable, flexible and maintainable. These five principles are described by Robert C. Martin. SOLID principles are briefly as follows:

  • S — Single Responsibility Principle (known as SRP)
  • O — Open/Closed Principle
  • L — Liskov’s Substitution Principle
  • I — Interface Segregation Principle
  • D — Dependency Inversion Principle

Why should we use SOLID principles in software development?

If developers design and implement their codes without using structured design principles such as SOLID principles, they will create long-lasting problems for other developers that will want to work on the project in future. So, the probability of success for this type of software will be diminished in future. This issues are commonly referred to Software Rot.

What is the meaning of Software Rot?

Software Rot or Code Rot or Software Erosion is either a slow deterioration of software quality over time or its decreasing responsiveness, which will eventually lead to software becoming faulty, unusable, and in need of upgrade.

Some signs for identifying Software Rot

  1. Rigidity: small changes leads to rebuild the entire software.
  2. Fragility: changes to one component causes other unrelated components to misbehave.
  3. Immobility: if a component cannot be used in another system, this component is considered as immobile. Immobility usually is caused by couplings and dependencies among various components.

4. Viscosity: when implementing and testing are difficult to perform, and also take a long time to execute.

Single Responsibility Principle (known as SRP)

The Single Responsibility Principle indicates that every class should have one and only one responsibility. In other words, if our class does more than one responsibility, we should split the functionalities and responsibilities in various classes. Therefore, this makes the class more robust.

For instance, in the below example, if the User class included the UserType class attributes and behaviors, it would not be a good design for this class because in this situation class has more than one responsibility. So, if we want to change any responsibility, we will probably have to change the other responsibilities.

public class User{
private String firstName;
private String lastName;

public String getFirstName(){
return firstName;
}

public void setFirstName(String firstName){
this.firstName = firstName; }

public String getLastName(){
return lastName;
}

public void setLastName(String lastName){
this.lastName = lastName; }
}

public class UserType extends User{
private int selecting;
public int getSelecting(){
return selecting; }

public void setSelecting(int selecting){
this.selecting = selecting; }

public boolean isSpecialUser(){
return selecting == 7; }
}

Open/Closed Principle

In object-oriented concepts, the Open/Closed principle states “software entities such as classes, components, modules should be open for extension, but closed for modification”; that is, such an entity can allow its behavior to be extended without modifying its source code.

Probably, these two parts of the Open/Closed principle appear to be contradictory, but if you correctly and accurately structure your classes and their dependencies, you can be able to add functionality without editing existing source code. In general, you achieve this principle by referring to Abstraction in Object-Oriented concepts for dependencies such as interfaces or abstract classes, rather than using concrete classes. Besides, functionality can be added by creating new classes that implement the interfaces. By using this approach, you can reduces the risk of new bugs to existing code, and it leads to more robust application. For instance, the below classes could be changed to the second one by using this principle.

public class Triangle{    public double base;
public double height;
}
public class Square{ public double side;
}
public class AreaCalculate{ public double getTriangleArea(Triangle triangle){ return area = (triangle.base * triangle.height)/2;
}

public double getSquareArea(Square square){
return square.side * square.side; }

After using this principle:

public interface Shape{    public double getArea();}
public class Triangle implements Shape{
double base;
double height;
public double getArea(){ return (base * height)/2;
}
}
public class Square implements Shape{ public double side; public double getArea(){ return side * side; }
}

public class AreaCalculate{
public double getShapeArea(Shape shape){ return shape.getArea(); }
}

Liskov’s Substitution Principle

This principle indicates that parent classes should be easily substituted with their child classes without changing the behavior of software. It means that a sub-class should override the methods from a parent class, which does not break the functionality of the parent class. For instance, in the following example each Fragment wants to implement the Interface. Thus, you should handle the requirements and changes in their class. In short, in the main implementation we should never manage logic.

public interface ClickListener{
public void onClick(); }

public class SampleFragment implements ClickListener{
@Override
public void onClick(){
decrementClickCount(); //You should manage the logic here!
}

public void decrementClickCount(){

}
}

public class TestFragment implements ClickListener{
@Override
public void onClick(){
incrementClickCount(); //You should manage the logic here!
}

public void incrementClickCount(){

}
}

public void onButtonClick(ClickListener clickListener){
//Handling the changes and requirements here would be a wrong place and an incorrect solution! clickListener.onClick(); }

Interface Segregation Principle

The interface-segregation principle indicates classes that implement interfaces, should not be forced to implement methods they do not use. This principle is related to the fact that a lot of specific interfaces are better than one general-purpose interface. In addition, this is the first principle, which is used in interface, all the previous principles applies on classes. For instance, If we have the below interface, it will force clients to implement onLongClick even if they don not need long press. Therefore, it can lead to overhead of unused methods. In a word, it could be helpful for ignoring the unused methods by having two separate interfaces. For example:

public interface MyOnClickListener{    void onClick(View v);
boolean onLongClick(View v);
void onTouch(View v, MotionEvent event);
}

After using this principle:

public interface OnClickListener{

void onClick(View v);
}

public interface OnLongClickListener{

boolean onLongClick(View v);
}public interface OnTouchListener{

void onTouch(View v, MotionEvent event);
}

Dependency Inversion Principle

This principle suggests that high level modules should not depend on low level. So, both should depend on abstractions. Furthermore, abstraction should not depend on details. Details should depend upon abstractions.

In a word, classes should depend on abstraction, but not on concretion. For example, In below example a new abstraction layer is added through the IEngine Interface for removing the dependency between two classes.

public class Engine{ }

public class ToyCar{
Engine engine;
ToyCar(){
Engine = new Engine(); }
}

After using this principle:

public interface IEngine{
}

public class Engine implements IEngine{
double capacity;
}

public class ToyCar{
IEngine iEngine;
ToyCar(IEngine engine){
iEngine = engine;
}
}

Another example in Android can be mentioned here is if we want to implement MVP design pattern, we need to keep reference of Presenter in our View. As a result, if we keep the Presenter concrete class object in View, it causes to tight coupling. Basically, we should create an interface for decoupling in this situation. Additionally, there are some popular libraries for implementing this principle in Android such as Dagger 2.

In conclusion

Fundamentally, using SOLID principles in Android development could be helpful to follow clean code principles. Thus, if Android developers design and implement their codes without using structured design principles such as SOLID principles, they will create long-lasting problems, and probability of success for an application will be decreased in future. This essay aims to discussed the importance of SOLID principles for Android developers.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb