SOLID Principles

Umut Yasin Colak
Insider Engineering
6 min readMay 22, 2023

Today, we will all remember together the SOLID principles that we all know less about, implement or try to implement.

We built the structure of our web projects with the Page Object Model (POM) structure, but we could not proceed with POM in our API automation. At this point, we started to implement the SOLID principles that we used to while writing POM and started to pay more attention to them. Although we have completely moved away from the POM structure, we have established a very nice structure with SOLID principles. We have built a structure that can be read well, developed, and understood no matter when. Let’s examine the universal SOLID principles that we have taken into account while writing our project together.

SOLID is a set of design principles that provide guidelines for creating high-quality, flexible, maintainable, and modifiable software systems. The SOLID principles were first introduced in the early 2000s by Robert C. Martin and consist of five widely accepted principles in software architecture and design. These principles were established after a software architecture conference. The SOLID principles have since been widely accepted as fundamental rules for software design and architecture, and are commonly used by software developers to create better, more flexible, and easily maintainable software systems.

SOLID is an acronym that stands for five design principles intended to make software designs more understandable, flexible, and maintainable.

The SOLID principles are:

  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)

These principles provide a set of guidelines for writing clean and maintainable code that can easily evolve over time. They help to avoid common problems in software design such as tight coupling, fragile systems, and difficulty changing or extending existing code.

We can briefly describe the SOLID principles as follows.

Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning that it should have only one responsibility.

class Hero:
def __init__(self, name, power):
self.name = name
self.power = power

def use_power(self):
print(f"{self.name} uses {self.power}")

class Weapon:
def __init__(self, name):
self.name = name

def attack(self):
print(f"{self.name} attacks with weapon")

class HeroWithWeapon(hero, attack, wepon):
def attack(self):
Hero(name=hero,power=attack)
Wepon(name=wepon)
print(f"My {hero}")

hero = HeroWithWeapon("Iron Man", "Super Strength", "Blaster")
hero.attack()

The compliance with SRP in this code can be explained as follows:

The Hero class only represents a hero’s name and power.
The Weapon class only represents the name of a weapon and how the weapon will attack.
The HeroWithWeapon class represents a hero attacking with a weapon.
The attack method of the HeroWithWeapon class allows a hero to attack using their power and weapon.
Considering the compliance with SRP in this code, each class takes on only a specific responsibility, and the HeroWithWeapon class brings together the Hero and Weapon classes to enable the hero to attack with a weapon. This way, each class has only one responsibility, and the code becomes more flexible and maintainable.

Open/Closed Principle (OCP)

Software entities should be open for extension but closed for modification.

class Ability:
def use(self):
pass
class Fly(Ability):
def use(self):
print("Flying")
class SuperStrength(Ability):
def use(self):
print("Super Strength")
class Hero:
def __init__(self, name, abilities):
self.name = name
self.abilities = abilities

def use_abilities(self):
for ability in self.abilities:
ability.use()
abilities = [Fly(), SuperStrength()]
hero = Hero("Superman", abilities)
hero.use_abilities()

The compliance with OCP in this code can be explained as follows:

The Ability class abstracts a skill and enables the use of each ability with the use method. Fly and SuperStrength classes represent the ability to fly and use super strength, respectively, by inheriting from the Ability class. The Hero class represents the name and abilities of a hero. The use_abilities method of the Hero class enables the hero to use all of their abilities. Considering compliance with OCP in this code, the Ability class makes all abilities usable by abstracting all abilities. Additionally, Fly and SuperStrength classes can be extended by simply adding a new class instead of being modified if a new ability is added. Therefore, the code becomes closed for modification and open for extension.

Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.

class Superhero:
def use_power(self):
pass
class Superman(Superhero):
def use_power(self):
print("Super Strength")
class WonderWoman(Superhero):
def use_power(self):
print("Invisible Plane")
def demonstrate_power(superheroes):
for superhero in superheroes:
superhero.use_power()
superheroes = [Superman(), WonderWoman()]
demonstrate_power(superheroes)

The compliance with LSP in this code can be explained as follows:

The Superhero class represents the common features of all superheroes and defines the use_power method as an abstract method.
The Superman and WonderWoman classes inherit from the Superhero class and adapt the use_power method according to their own powers.
The demonstrate_power function allows each superhero in the superheroes list to use their power.
In compliance with LSP, the Superman and WonderWoman classes should be substitutable for the Superhero class and behave like a Superhero object wherever they are used. That is, both classes should be compatible with the usage in the Superhero class. This makes the code more flexible and maintainable.

Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use.

class Superhero:
def use_power(self):
pass
class FlyingSuperhero(Superhero):
def fly(self):
pass
class AquaticSuperhero(Superhero):
def swim(self):
pass
class Superman(FlyingSuperhero, AquaticSuperhero):
def use_power(self):
print("Super Strength")
def fly(self):
print("Superman is flying")
def swim(self):
print("Superman is swimming")

The compliance with ISP in this code can be explained as follows:

The Superhero class represents the common characteristics of all superheroes and defines the use_power method as abstract. The FlyingSuperhero and AquaticSuperhero classes inherit from the Superhero class and represent the flying and swimming features, respectively. The Superman class inherits from the FlyingSuperhero and AquaticSuperhero classes, representing a superhero who can use both flying and swimming abilities. The Superman class also uses the use_power method of the Superhero class to use his powers like other superheroes. Considering compliance with ISP in this code, the FlyingSuperhero and AquaticSuperhero classes should be designed with interfaces that allow each class to contain only its own characteristics. This way, a superhero with the flying feature can be implemented separately from the AquaticSuperhero class. This makes the code more flexible and easier to extend.

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

class Superhero:
def use_power(self):
pass
class Power:
def use(self):
pass
class Flight(Power):
def use(self):
print("Flying")
class Strength(Power):
def use(self):
print("Super Strength")
class Superman(Superhero):
def __init__(self, powers):
self.powers = powers

def use_power(self):
for power in self.powers:
power.use()
superman = Superman([Flight(), Strength()])
superman.use_power()

The compliance with DIP in this code can be explained as follows: The Superhero class represents the common features of all superheroes and defines the use_power method as abstract. The Power class is introduced as a general interface for all superpowers and defines the use method as abstract. The Flight and Strength classes implement the Power interface and provide specific implementations for the use method for flight and super strength, respectively. The Superman class depends on the Power interface instead of concrete classes and receives a list of powers through its constructor. It then iterates over the list and calls the use method of each power to execute the corresponding superpower. By introducing the Power interface and making the Superman class depend on it, rather than on concrete classes, this code follows the DIP. This allows for more flexibility and extensibility, as new superpowers can be easily added by creating new classes that implement the Power interface, without needing to modify the Superman class.

In this article, we revisited the SOLID principles that we are all familiar with and have often applied and provided examples using Python. I used examples from the superhero universe so that we can all relate to it. To make a topic more memorable, it helps to associate it with a favorite movie, cartoon, novel, or any other subject that you are passionate about.

Conclusion

In conclusion, the SOLID principles are an invaluable tool for software designers. They help to create code that can be easily maintained and extended, allowing developers to create applications that are both powerful and efficient. The principles can be made even more memorable and enjoyable to learn by using examples and analogies that we can all relate to, such as the superhero universe used in this article.

It is important to keep in mind that applying the principles correctly can take time and practice, but the rewards are worth the effort. The improved code design will help to ensure that applications remain reliable and robust while also being easier to understand and maintain. Ultimately, using the SOLID principles can save time and money while producing a high-quality product.

Finally, I’m pleased to be a part of the Insider and very excited about the work we will do in the future. If you would like to learn how we manage QA Process at Insider, you can read this article. https://medium.com/insiderengineering/quality-at-insider-an-in-depth-look-at-our-qa-process-1ea03f7d8655

For any questions, you can reach me at the email address provided below: https://www.linkedin.com/in/umut-yasin-çolak/

--

--