An introduction to the SOLID principles
Kriptofolio app series - Part 1
Software is always in a state of change. Each change can have a negative impact on a whole project. So the essential thing is to prevent damage that can be done while implementing all new changes.
With “Kriptofolio” (previously “My Crypto Coins”) app, I will be creating a lot of new code step by step and I want to start doing that in a good way. I want my project to be solid quality. First we need to understand the foundation principles of creating modern software. They are called SOLID principles. Such a catchy name! 🙂
Series content
- Introduction: A roadmap to build a modern Android app in 2018–2019
- Part 1: An introduction to the SOLID principles (you’re here)
- Part 2: How to start building your Android app: creating Mockups, UI, XML layouts
- Part 3: All about that Architecture: exploring different architecture patterns and how to use them in your app
- Part 4: How to implement Dependency Injection in your app with Dagger 2
- Part 5: Handle RESTful Web Services using Retrofit, OkHttp, Gson, Glide and Coroutines
Principles slogan
SOLID is a mnemonic acronym. It helps to define the five basic object-oriented design principles:
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Next we are going to discuss each of them individually. For each, I am going to provide bad vs good code examples. These examples are written for Android using the Kotlin language.
Single Responsibility Principle
A class should have only a single responsibility.
Each class or module should be responsible for one part of the functionality provided by the app. So when it handles one thing, there should be only one main reason to change it. If your class or module does more than one thing, then you should split the functionalities in separate ones.
For a better understanding of this principle, I would take as an example a Swiss Army knife. This knife is well known for its multiple functions besides its main blade. It has other tools integrated inside, such as screwdrivers, a can opener, and many others.
The natural question here for you is why am I suggesting this knife as an example for single functionality? But just think about it one moment. The other main feature of this knife is mobility while being pocket size. So even if it offers a few different functions it still fits its main purpose to be small enough to take it with you comfortably.
The same rules go with programming. When you create your class or module, it has to have some main global purpose. At the same time you can’t overplay when trying to simplify everything too much by separating functionality. So remember, keep the balance.
A classic example could be the often used method onBindViewHolder
when building RecyclerView widget adapter.
👿 BAD CODE EXAMPLE:
🙂 GOOD CODE EXAMPLE:
Code specifically designed with the Single Responsibility Principle in mind will be close to the other principles that we are going to discuss.
Open-Closed Principle
Software entities should be open for extension, but closed for modification.
This principle states that when you write all the software parts like classes, modules, and functions, you should make them open for extension but closed for any modification. What does that mean?
Let’s say we create a working class. There should be no need to tweak that class if we need to add new functionality or do some changes. Instead we should be able to extend that class by creating its new subclass where we could easily add all new necessary features. Features should always be parameterized in a way that a subclass can override.
Let’s take a look at an example where we will create a special FeedbackManager
class to show a different type of custom message for the user.
👿 BAD CODE EXAMPLE:
🙂 GOOD CODE EXAMPLE:
The open-closed principle summarizes the goals of the next two principles that I talk about below. So let’s move on to them.
Liskov Substitution Principle
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
This principle is named after Barbara Liskov — an accomplished computer scientist. The general idea of this principle is that objects should be replaceable by instances of their subtypes without changing the behavior of the program.
Let’s say in your app you have MainClass
which depends on BaseClass
, which extends SubClass
. In short, to follow this principle, your MainClass
code and your app in general should work seamlessly without any problems when you decide to change BaseClass
instance to the SubClass
instance.
To understand this principle even better, let me give you a classical, easy to understand example with Square
and Rectangle
inheritance.
👿 BAD CODE EXAMPLE:
🙂 GOOD CODE EXAMPLE:
Always think before writing down your hierarchy. As you see in this example, real-life objects do not always map to the same OOP classes. You need to find a different approach.
Interface Segregation Principle
Many client-specific interfaces are better than one general-purpose interface.
Even the name sounds complicated, but the principle itself is rather easy to understand. It states that a client should never be forced to depend on methods or implement an interface that it doesn’t use. A class needs to be designed to have the fewest methods and attributes. When creating an interface try not to make it too big. Instead split it into smaller interfaces so that client of the interface will only know about the methods that are relevant.
To get the idea of this principle I have created again bad vs good code examples with butterfly and humanoid robots. 😉
👿 BAD CODE EXAMPLE:
🙂 GOOD CODE EXAMPLE:
Dependency Inversion Principle
One should “depend upon abstractions, [not] concretions.”
The last principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
The main idea of the principle is not to have direct dependencies between the modules and classes. Try to make them dependent on the abstractions (e.g. interfaces) instead.
To simplify it even more, if you use a class inside another class, this class will be dependent on the class injected. This violates the principle’s idea and you should not do that. You should try to decouple all classes.
👿 BAD CODE EXAMPLE:
🙂 GOOD CODE EXAMPLE:
To sum up briefly
If we think about all these principles, we can notice that they are complementary to each other. Following the SOLID principles will give us many benefits. They will make our app reusable, maintainable, scalable, testable.
Of course it is not always possible to follow all these principles completely, as everything depends on individual situations when writing code. But as a developer you should at least know them so you can decide when to apply them.
Repository
This is the first part where we learn and plan our project instead of writing new code. Here is a link to Part 1 branch commit, which basically is “Hello world” initial code of the project.
View Source On GitHub
I hope I managed to explain SOLID principles well. Feel free to leave comments below.
Ačiū! Thanks for reading! I originally published this post for my personal blog www.baruckis.com on February 23, 2018.