Skip to content

SOLID Principles

Published: at 12:00 AM

SOLID - A practical explanation

The principles by Uncle Bob

These principles are designed to make development more readable, predictable and maintainable, they are:

S - Single-responsibility principle: “There should never be more than one reason for a class to change.” In other words, every class should have only one responsibility.

O - Open-closed principle: “Software entities … should be open for extension, but closed for modification.”

L - Liskov substitution principle: “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”

I - Interface segregation principle: “Clients should not be forced to depend upon interfaces that they do not use.”

D - Dependancy inversion principle: “Depend upon abstractions, [not] concretions.”

That sounds like a bunch of jargon - because it is. I’ll explain further below what it means for me.

S - Single-responsibility principle

I’m sure we’re all familiar with this:

This principle means that a class (or module, function, or whatever) - only has one reason to change.

In Flutter this could mean that a widget should be responsible only for piece of the UI. For example, if you have a widget that displays user data, it shouldn’t also be responsible for fetching that data. That fetching job should be delegated to a provider or service.

Goal: Understanding and Managing Changes - changes should only have a local impact, if a change in billing also impacts user authentication that’s bad software.

O - Open-closed principle

This second one is both a reversal and extension of the first rule - SRP. Consider below:

If we were adhering strictly to SRP everytime - we’d always be making Edward Scissorhands, instead of a person who can use a lot of things including scissors.

This means that we should be writing our modules in a way that doesn’t require us to refactor later if we want to add on a new module, or extend the functionality of that module.

For Flutter this would mean creating widgets that are easily extensible through composition. For example, if you have a button widget and later need a button with an icon, instead of modifying the original button, you can create a new widget that combines the original button with an icon.

Goal: Maintaining flexibility - by adhering to OCP then we can easily extend the functionality of new and existing modules.

L - Liskov substitution principle

Now this one is a bit harder to explain, so remember this scene?

In the movie both parents are black, but the child brought to the prison is white. This is the essence of LSP - a child object of a parent object needs to be able to behave in the exact same way.

Basically what it means is the lower-level objects and higher-level objects work the exact same way if they are part of the same class. The only use of a higher-level object is to extend the behaviour of the lower-level object without changing it’s expected behaviour.

Just like if both parents are black, you don’t get a white baby. That means that the white baby (as a higher-level object) of the parents (lower-level objects) violates the LSP.

Goal: Maintaining consistency in behaviour

I - Interface-segregation principle

This principle is actuall quite straightforward:

The point of ISP is that you break down large interfaces in favour of smaller interfaces. If you display all of the information in one go you risk overwhelming the user.

Break it down for them in chunks so the input is more accurate and the users are more attentive to the smaller details.

Goal: Reduce unnecessary implementation - and risk overwhelming the user or giving them unauthorised access

D - Dependancy-inversion principle

This one actually took the most time for me to understand.

Anyway the best way I describe it is to think of a tree:

When we are talking abstractions we are talking about in between layers that allow the layers of each application to be de-coupled from each other.

Now let’s imagine this tree had different colours depending on how old it was, because it needed to camoflauge itself from predators depending on the seasons. Imagine that the season requires the tree to go from red to blue, but that the tree grows in the colour red, and matures to blue. So the tree has these properties:

Instead of thinking of abstractions as a way to understand the tree by removing the colour element (which is impossible - like abstracting large concepts from an entire application accurately), it is seen as a half way “save point” if you will. For each layer, it would be as such:

Likewise - let’s say the seasons switched and the tree had to change from blue to red again. The process is same but instead of the colour at the bottom is blue, it’s now red. The whole process has been flipped and so the abstractions help speed up the process.

Or think about it like this - in terms of an intelligence agency such as the CIA:

1. Presentation Layer (Field Agents):

2. Abstraction between Presentation and Logic (Mission Briefing):

3. Logic Layer (Analysts):

4. Abstraction between Logic and Data (Secure Communication Channels):

5. Data Access Layer (Data Repositories):

6. Abstraction between Data Access and Database (Not needed in Firestore, but let’s imagine):

7. Database (The Secure Vault):

Goal: It allows for easier replacement / movement of layers. Replacing one layer won’t destroy the whole workflow.

Consider supporting this blog