Android Development: the SOLID Principles

Abderrazak Laanaya
AndroidPub
Published in
8 min readJun 5, 2016

--

SOLID is a mnemonic acronym that helps define the five basic object-oriented design principles:

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

SOLID #1: The Single Responsibility Principle (SRP)

A class should have one, and only one, reason to change.

for more details, let’s assume we need an adapter of Recyclerview, as you probably already know an adapter takes the data from the data set and adapts it to a view. An implementation I’ve seen could look like this:

// violation of single responsibility principlepublic class MovieRecyclerAdapter extends RecyclerView.Adapter<MovieRecyclerAdapter.ViewHolder> {

private List<Movie> movies;
private int itemLayout;

public MovieRecyclerAdapter(List<Movies> movies, int itemLayout)
{
this.movies = movies;
this.itemLayout = itemLayout;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View v = LayoutInflater.from(parent.getContext())
.inflate(itemLayout, parent, false);
return new ViewHolder(v);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Movie movie = movies.get(position);
holder.itemView.setTag(movie);
holder.title.setText(movie.getTitle());
holder.rating.setText(movie.getRating());
String genreStr = "";
for (String str: movie.getGenre()) {
genreStr += str + ", ";
}
genreStr = genreStr.length() > 0 ?
genreStr.substring(0, genreStr.length() - 2) : genreStr;
holder.genre.setText(genreStr);
holder.releaseYear.setText(movie.getYear());
Glide.with(holder.thumbNail.getContext())
.load(movies.get(position)
.getThumbnailUrl())
.into(holder.thumbNail);
}

@Override
public
int getItemCount() {
return movies.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.title) TextView title;
@Bind(R.id.rating) TextView rating;
@Bind(R.id.genre) TextView genre;
@Bind(R.id.releaseYear) TextView releaseYear;
@Bind(R.id.thumbnail) ImageView thumbNail;

public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}

The code above violates the Single Responsibility Principle. Coz the adapter’s onBindViewHolder method is not only mapping from an Movie object to the view, but is also performing data formatting as well. This violates the Single Responsibility Principle. The adapter should only be responsible for adapting an order object to its view representation. The onBindViewHolder is performing extra duties that it should not be. An updated onBindViewHolder method could look like this:

// single responsibility principle - Fix it examplepublic class MovieRecyclerAdapter extends RecyclerView.Adapter<MovieRecyclerAdapter.ViewHolder> {

private List<Movie> movies;
private int itemLayout;

public MovieRecyclerAdapter(List<Movie> movies, int itemLayout)
{
this.movies = movies;
this.itemLayout = itemLayout;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View v = LayoutInflater.from(parent.getContext())
.inflate(itemLayout, parent, false);
return new ViewHolder(v);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Movie movie = movies.get(position);
holder.itemView.setTag(movie);
holder.title.setText(movie.getTitle());
holder.rating.setText(movie.getRating());
holder.genre.setText(
ArraysUtil.convertArrayListToString(movie.getGenre()));
holder.releaseYear.setText(movie.getYear());
Glide.with(holder.thumbNail.getContext())
.load(movies.get(position)
.getThumbnailUrl())
.into(holder.thumbNail);
}

@Override
public
int getItemCount() {
return movies.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.title) TextView title;
@Bind(R.id.rating) TextView rating;
@Bind(R.id.genre) TextView genre;
@Bind(R.id.releaseYear) TextView releaseYear;
@Bind(R.id.thumbnail) ImageView thumbNail;

public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}

As Uncle Bob said:

In the context of the Single Responsibility Principle (SRP) we define a 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.

SOLID #2: The Open-Closed Principle (OCP)

Software entities (classes, modules, functions, etc…) should be open for extension, but closed for modification

What we are basically talking about here is to design our modules, classes and functions in a way that when a new functionality is needed, we should not modify our existing code but rather write new code that will be used by existing code. let’s dive to it, and show some code:

// violation of Open closed principle
// Rectangle.java
public class Rectangle {
private double length;
private double height;
// getters/setters ...
}
// Circle.java
public class Circle {
private double radius;
// getters/setters ...
}
// AreaFactory.java
public class AreaFactory {
public double calculateArea(ArrayList<Object>... shapes) {
double area = 0;
for (Object shape : shapes) {
if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle)shape;
area += (rect.getLength() * rect.getHeight());
} else if (shape instanceof Circle) {
Circle circle = (Circle)shape;
area +=
(circle.getRadius() * cirlce.getRadius() * Math.PI);
} else {
throw new RuntimeException("Shape not supported");
}
}
return area;
}
}

As we see above, this code smell like if we have a shape like Triangle or any other polygone, we’re going to be modifying the AreaFactory class over and over. And that’s violate the open closed principle. It is not closed for modification and it is not open to extension. And that’s really bad, so let’s fix that:

// Open closed principle: good example
// Shape.java
public interface Shape {
double getArea();
}
// Rectangle.java
public class Rectangle implements Shape{
private double length;
private double height;
// getters/setters ...
@Override
public double getArea() {
return (length * height);
}
}
// Circle.java
public class Circle implements Shape{
private double radius;
// getters/setters ...
@Override
public double getArea() {
return (radius * radius * Math.PI);
}
}
// AreaFactory.java
public class AreaFactory {
public double calculateArea(ArrayList<Shape>... shapes) {
double area = 0;
for (Shape shape : shapes) {
area += shape.getArea();
}
return area;
}
}

Now, if we need to add a new shape, the AreaFactory will not need to be changed because it is open for extension through the Shape interface.

SOLID #3: The Liskov Substitution Principle (LSP)

Child classes should never break the parent class’ type definitions.

As simple as that, a subclass should override the parent class’ methods in a way that does not break functionality from a client’s point of view. Here is a simple example to demonstrate the concept.

// violation of Liskov's Substitution principle// Car.java
public interface Car
{
public void startEngine();
}
// Ferrari.javapublic Ferrari implements Car {
...
@Override
public
double startEngine() {
//logic ...
}
}
// Tesla.java
public Tesla implements Car{
...
@Override
public
double startEngine() {
if (!IsCharged)
return;
//logic ...

}
}
// Make the call
public void letStartEngine(Car car) {
car.startEngine();
}

As you can see in the code above, there are two classes of cars. One fuel car and one electric car. The electric car can only start if it’s charged .The LetStartEngine method will not work if a car is electric and not charged. This breaks the LSP principle since it must be Charged to be able to start as the IsCharged (which also is part of the contract) won’t be set as in the base class.

To solve this you can do something like this:

// Make the call
public void LetStartEngine(Car car) {
if (car instanceof Tesla)
((Tesla)car).
TurnOnCar();
car.startEngine();
}

But this violates the Open/Closed Principale, so the proper way is to automatically turn on the car in the StartEngine method like below:

// Fix of Liskov's Substitution principlepublic interface Car {
public void startEngine();
}
// Ferrari.javapublic Ferrari implements Car {
...
@Override
public
double startEngine() {
//logic ...
}
}
// Tesla.java
public Tesla implements Car{
...
@Override
public
double startEngine() {
if (!IsCharged)
TurnOnCar();
//logic ...

}
}
// Make the call
public void letStartEngine(Car car) {
car.startEngine();
}

SOLID #4: The Interface Segregation Principle (ISP)

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

This principale states that once an interface becomes too fat, it needs to be split into smaller interfaces so that client of the interface will only know about the methods that pertain to them. As you know, the Android View class is the root superclass for all Android views. You name it, if it’s a Button, the root superclass is View. Let’s dive to it, and show some code:

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

As you can see, this interface contains three diffrent methods, assuming that we wanna get click from a button:

// Violation of Interface segregation principle
Button valid = (Button)findViewById(R.id.valid);
valid.setOnClickListener(new View.OnClickListener {
public void onClick(View v) {
// TODO: do some stuff...

}

public void onLongClick(View v) {
// we don't need to it
}

public void onTouch(View v, MotionEvent event) {
// we don't need to it
}
});

The interface is too fat because it’s forcing to implement all the methods, even if it doesn’t not need them. Let’s trying to fix them using ISP:

// Fix of Interface Segregation principle
public
interface OnClickListener {
void onClick(View v);
}
public interface OnLongClickListener {
void onLongClick(View v);
}
public interface OnTouchListener {
void onTouch(View v, MotionEvent event);
}

know we can use the interface without implement some messy methods.

SOLID #5: The Dependency Inversion Principle (DIP)

1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend upon details. Details should depend upon abstractions.

Let’s start from the code. Many of us probably seen (or written) code like this:

// violation of Dependency's inversion principle// Program.java
class Program {

public void work() {
// ....code
}
}

// Engineer.java

class Engineer{

Program program;

public void setProgram(Program p) {
program = p;
}

public void manage() {
program.work();
}
}

The problem with the above code is that it breaks the Dependency Inversion Principle ; namely item (1.) from above: High-level modules should not depend on low-level modules. Both should depend on abstractions. We have the Engineer class which is a high level class, and the low level class called Program.

Let’s assume the Engineer class is quite complex, containing very complex logic. And now we have to change it in order to introduce the new SuperProgram. Let’s see the disadvantages:

  • we have to change the Engineer class (remember it is a complex one and this will involve time and effort to make the changes).
  • some of the current functionality from the engineer class might be affected.
  • the unit testing should be redone.
// Dependency Inversion Principle - Good example
interface IProgram {
public void work();
}

class Program implements IProgram{
public void work() {
// ....code
}
}

class SuperProgram implements IProgram{
public void work() {
//....code
}
}

class Engineer{
IProgram program;

public void setProgram(IProgram p) {
program = p;
}

public void manage() {
program.work();
}
}

In this new design a new abstraction layer is added through the IProgram Interface. Now the problems from the above code are solved(considering there is no change in the high level logic):

  • Engineer class doesn’t require changes when adding SuperProgram.
  • Minimized risk to affect old functionality present in Engineer class since we don’t change it.
  • No need to redo the unit testing for Engineer class.

As conclusion, we can said that the Single Responsibility Principle is about actors and high level architecture. The Open/Closed Principle is about class design and feature extensions. The Liskov Substitution Principle is about subtyping and inheritance. The Interface Segregation Principle (ISP) is about business logic to clients communication. And the Dependency Inversion Principale (DIP) is about leads or helps us respect all the other principles.

Thanks for sharing :)

--

--