S.O.L.I.D Principles… Time for me to actually learn them.

dargoyaki
7 min readNov 10, 2020

--

Throughout my Computer Science journey in NUS, time and time again I’ve come across the concept of S.O.L.I.D when it comes to programming. Back when I was a wee freshman, the concept of S.O.L.I.D first came up in my second semester. Of course, being the lost and intellectually infantile freshman that I was, I didn’t internalize anything about it. So how does this relate back to this post? I’m currently in my 3rd year and unfortunately, my current module requires me to write a blogpost about concepts taught in the mod. And lo’ and behold, S.O.L.I.D comes back to haunt me once more. It was at this point I thought to myself, ‘what better way to FINALLY learn about the S.O.L.I.D principles than to write a technical blogpost about it?’ Well, probably actually putting them into practice through programming, but that ain’t gonna get me the marks for this assignment! But I digress. I’m supposed to be talking about S.O.L.I.D, so… let’s get to it.

In this article, I’ll try to explain these in the most layman way to make it as readable as possible. My aim is to ensure that anyone with programming background or not would be able to better understand this concept of S.O.L.I.D. No need to get too fancy schmancy with the Computing lingo in here. I will also be utilising diagrams and examples to illustrate the principles because ‘a picture speaks a thousand words’ (also because the rubrics require them…).

So, what are the S.O.L.I.D principles? Simply put, they are a bunch of programming principles proposed by big brain computer scientists. These principles are typically practiced in the realm of Object-Oriented Programming.

‘But, why should we follow them?’

Well, glad you asked. The S.O.L.I.D principles serve as a design ‘rules’ to ensure that created software is easy to maintain, test, and add onto. Personally, I think they are there to give a certain level of organization and structure to the software. Think of it like organizing a clothes drawer. Dumping clothes in with no concept of folding is sure to work without issue, at least for a while. The problem starts coming in when you try pick out something to wear. Suddenly, pulling out a shirt messes up the whole layout of the drawer. Testing out an outfit becomes tedious. Adding onto the pile of clothes is hard due to the lack of space optimization in the drawer. Now, what if you folded them up, Marie Kondo style, and put in the time and effort to organize them properly? Taking out a shirt becomes a whole lot easier without affecting the other clothes. Testing outfits become a lot easier. Adding in a shirt? No problem!

Through this bad example, you can see how easy things are when you organize them with design ‘rules’ in mind. When following good principles, you reduce the dependencies that the parts of your code have with one another. Pulling out a shirt barely affects another when organized due to their reduced dependency, as compared to if you pile them up together with no format in mind. Testing code is a lot easier as well, and adding on to the current software can be done easily. That’s the power of S.O.L.I.D. Of course, it’ll take more effort and time to implement software using these principles, but ultimately the payoff is worth it. Your software becomes a lot more robust, effective, flexible and adaptive. Just like how your drawer becomes a lot easier to manage when you put in the effort to organize it.

These are the 5 principles comprising S.O.L.I.D:

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov’s Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Single Responsibility Principle (SRP)

What is it?

Each class should only have a single responsibility.

Why?

With a singular responsibility, we essentially prevent bugs that may come up which may arise from having multiple responsibilities.

How so?

Let’s say a certain class has 2 responsibilities, A & B. If I make changes to A, this could potentially affect the responsibility of B as data in the class gets changed. By making each class have a sole responsibility, we essentially negate the possibility of such a case happening.

Hypothetical Example

We have a class TextChangerAndPrinter that has 2 responsibilities, changing text and printing text. This class violates SRP, as it should only have 1. Instead, we want to split this class into 2 classes: TextChanger & TextPrinter, which have responsibilities changing text and printing text respectively.

Open Closed Principle (OCP)

What is it?

A class should be extendable but closed to modification. Added functionality should add onto the current functions, but NOT change them.

Why?

We do not want to allow a class to be modified heavily as it will affect the behaviour of the instances of that class.

How so?

Let’s say you have a vehicle object ‘A’ that is unable to fly, and you create a bunch of vehicles based on ‘A’. Suddenly, you realise that a brand of vehicles is able to make the car fly. However, that does not necessarily mean that your other vehicles are able to fly. If you add functionality allowing ‘A’ to fly, that’ll affect all other vehicles, which botches the behaviour of your vehicle. What you want to do instead is create a new vehicle object ‘B’ for that brand which contains all the old behaviour of the normal vehicle, but adds on the ability to fly.

Hypothetical Example

Based on above. We have a class Vehicle that cannot fly. We also have a special brand of vehicle that is able to fly. To adhere to OCP, we create a class FlyingVehicle which extends Vehicle, such that it has all the behaviours of Vehicle, yet also adding on with it’s ability to fly.

Liskov’s Substitution Principle (LSP)

What is it?

A derived class must be substitutable for its base class. Basically, any child class that extends from a parent class should be able to behave in the same way as the parent.

Why?

LSP enforces consistency so that both the parent class and child class can be used in a similar way without errors.

How so?

Let’s say I use a coffee maker in order to make coffee. I then buy a premium coffee maker which also makes coffee, but in a more convoluted manner. Ultimately, I am still able to interchange from using my basic coffee maker and premium coffee maker as they both perform the same functionality of making coffee. This ensures that even if I interchange between classes, I am still able to get the intended behaviour.

Hypothetical Example

Based on above. We have 2 classes BasicCoffeeMaker and PremiumCoffeeMaker a subtype of BasicCoffeeMaker. Adhering to LSP allows BasicCoffeeMaker to be substituted with PremiumCoffeeMaker to create coffee, despite their different methods of doing so.

Interface Segregation Principle (ISP)

What is it?

Clients should not be forced to depend on methods that they do not use. If a class has no reason to be implementing a method from an interface, it should not be doing so.

Why?

It is wasteful and may produce bugs if the class does not have the ability to perform those actions. This principle ensures that classes only execute actions that it requires to.

How so?

Let’s say I implement the methods from a drinks maker into my coffee maker. However, my drinks maker also requires the ability to create iced tea, which my coffee maker has no need to do. That functionality becomes pointless and serves no purpose, and should be removed as unnecessary code would only further enhance the chance of bugs appearing in the code.

Hypothetical Example

Based on above. We have an interface DrinksMaker, and class CoffeeMaker which implements DrinksMaker. However, DrinksMaker also has a method named makeIcedTea, which is not required in CoffeeMaker. This violates ISP as there is no need for CoffeeMaker to make Iced Tea, and should be removed from the interface.

Dependency Inversion Principle (DIP)

WARNING: Example may not be very good.

What is it?

High-level modules should not depend on low-level modules. They should depend on abstractions. Details should also depend on abstractions.

Why?

So that we can reduce the dependency of a high-level Class on a low-level Class.

How so?

By allowing them to depend on abstractions instead of fusing them, this reduces dependency of high-level classes on low-level ones. This loose coupling would reduce bugs if one were to modify the code.

Hypothetical Example

I have a class named StudentInformation which has information classes Name, IQ, etc. To adhere to DIP, the StudentInformation class should not directly reference the whole Name class in order to retrieve the information, rather having an abstraction instead, where a method returns only the information required.

In Conclusion…

The S.O.L.I.D Principles are principles that have stood the test of time, and for good reason. They really damn solid. So do follow them.

--

--

dargoyaki
dargoyaki

Written by dargoyaki

Some final year computer science student from NUS that does a bit of YouTube on the side.

No responses yet