Programming principles — Android app architecture by example Part 2/5

Ashesh Bharadwaj

Learning how to use a tool is one aspect and creating a quality product using it is another aspect.

This is the second article of a five-article series on Android app architecture:

1. What is Architecture?
2. Programming principles (you are here)
3. Layered Architecture
4. Clean Architecture
5. Sample app

Android Studio, Java/Kotlin, Gradle, Dagger, RxJava etc. are all tools which help us to create a solution. Overall quality of the solution though does not depend on the tools. To bring quality to our solutions we need to take guidance from the programming principles.

Programming principles guide us to do things the right way. It’s the right mix of tools and programming principles which help us create an optimal solution.

The mother of all programming principles is “Separation of concerns”. Separation of concerns is focusing on a certain aspect of the software. This can be applied at function/class or higher levels. For example, a class which reads and writes data to a disk should not be concerned about how the data is processed. Its job should be only to read/write the data and the processing of the data should be handled by another class.

Why do we want to do that? Why the separation of concerns?

The answer lies in the fact that change is inevitable. If we were to release just one version of an app, we could overlook the programming principles and practices. It would have been sufficient just to make the app functional. This is never the case though. Apps keep changing as per the requirements of clients or users.

Change is inevitable

All the programming principles and best practices we apply is to prepare against the change. It’s not like “change” is bad but a change can bring errors to the app, a bug can creep in or the app can become unstable.

The change is inevitable so what we can do is, minimize the impact of the change by reducing the scope. A change which happens to one class or a function is much better than a change which creates ripples across the app. We separate function/classes/modules to reduce the impact of a change. Most of the programming principles we follow are directly or indirectly based on this separation.

One set of programming principles for object-oriented programming is SOLID introduced by Robert C. Martin in 2000. These principles are widely followed by developers all over the world.

SOLID is an acronym for the following principles:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Single Responsibility Principle (SRP)
A class should have only one reason to change

This principle takes direct guidance from the separation of concerns. A class should be responsible for one thing or we can say that there should be only one reason for it to change. Only one reason because if there are two reasons to change then the probability of an error or instability becomes twice and so on.

The easiest way to implement this principle is to divide a class into small functions and then group these functions together based on their proximity(cohesiveness). This way we can create multiple classes from a single class. All of them having a single responsibility. We need to be careful not to overdo it.

Let’s take an example, if we have two functions writeDataToDisk and readDataFromDisk, should they be in separate classes DataReader and DataWriter or a single class DataAccess? Both these functions are tied to the same data and that data would be the reason to change so it makes sense to keep them in the same class. Although a lot of other factors come into play with experience it becomes easy for us to decide.

Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification

A class should be open for extension but closed for modification. How can we extend without modifying the code? Here extend doesn’t mean the keyword extend but more like adapt to future changes. The answer lies in abstraction. Instead of a class depending on another concrete class, it should depend on an abstraction

For example, let’s say we have a ReportWriter class which calls getReportData() function of PdfReport to get the data and print it.

ReportWriter class calls PdfReport class function getReportData()

It is reasonable to assume that in near future we may need to print the report in text, xml or other formats. When that happens we will have to modify the ReportWriter class to adjust for any new format. That would be a violation of OCP, which says that a class should be closed for modification.

A better way to make our code future ready is to depend on abstraction.

Instead of ReportWriter directly depending on concrete class it depends on abstraction (interface)

We can declare an interface Report and make the concrete class PdfReport implement it. We can pass PdfReport as Report object to the ReportWriter class. This way ReportWriter will depend on abstraction and wouldn’t be concerned with the concrete class which is implementing that interface.

Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types

A child object should behave like its parent when passed as a parent. Let’s say Class B extends Class A. There is a function which requires Class A object but instead, if we pass Class B object (perfectly legal in object-oriented world) then Class B object should behave like Class A object.

For example, if Class A has a function parseData() which returns a String in JSON format. Class B extends class A and overrides parseData() where it returns a String without JSON formatting. Class B parseData() function will work fine where Class B object is required. The problem would happen if we pass Class B object where Class A object is required. The calling code would expect a JSON formatted string but instead would get a non formatted string.

This kind of violation brings instability to our apps.

Interface Segregation Principle (ISP)
Many client specific interfaces are better than one general purpose interface

There is a Service class which implements ServiceInterface. The ServiceInterface contains the functions required by its clients.

A large single interface serving multiple client

A better way to handle this is to divide the large single interface into multiple interface and let clients use them. This way clients would know about only those methods which it requires. Remember, separation of concerns?

Now Service class is implementing 3 interfaces instead of a single large interface. Other than that there is no change in Service class but the exposure to clients are now reduced to only those functions they require.

The large interface is broken down into smaller interfaces.

Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules

A high level class should not depend on low level class and both should depend on abstraction. Before going further let’s see what’s a high/low level class.

An Android app executes inside a boundary. Outside the boundary, there is the Android framework itself, various service manager etc. A class which is closest to the boundary is the lower level class. For example Activity/Fragment get events directly from the Android framework which is outside the boundary so it’s low level class. A low level class is based on implementation/details.

A high level class is more abstract, dealing with business logic (the code specific to your app). High level classes are less dependent on the platform/third party libraries.

High level class shouldn’t depend on low level class

Let’s say Repository class needs to get data from DB class. Here Repository is a higher level class than DB class because DB class is more dependent on detail (SQLite). Now, Repository class can’t directly depend on DB class as it would be a violation of DIP.

What we do is create an Interface DataInterface which is implemented by DB class and then Repository class uses an object of DataInterface . The data interface is an abstract class and at the same level as Repository so it’s fine for the Repository to depend on it.

Also if we see both Repository and DB class now depend on abstraction. This helps to loosely couple the classes. Another advantage is now that we have DataInterface , a network class can implement it and Repository class (without any modification) would be ready to get data from the network. Reminds you something of Open Closed Principle?

Once we have an understanding of SOLID principles the next step should be to study various design patterns. Design patterns are the ready-made solutions to implement these principles in our app. For example, Open Closed Principle can be implemented using a strategy pattern or template method pattern.

In the next part we will learn what‘s layered architecture.

Thanks for reading this article. You can connect with me on LinkedIn and Twitter

If you like this article, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.

Ashesh Bharadwaj

Written by

Google Certified Android Developer, enjoying the journey to the future of personal data space.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade