Mangal Yatri| Web Developer

Blogs


SOLID


Imperative Design principles in a nutshell.

The entire objective behind establishing the concept by Robert Martin and the Gang of Four was to make code more readable and extensible. The nuance being that the code is ultimately written for humans and not for the machine. This takes years to understand and essentially what distinguishes a senior SWE from juniors.

We are going to walk through these 5 principles and their applications and the need for them to be implemented in large scale codebases.

First and foremost we need to understand that we have to make a choice whether we have the need for these principles in the architecture in the first place and it can be really laborious task. There's no hard and fast rule to employ all the five principles as in most cases you wont even find them together in real life scaled architectures but making use of a combination of them can really make your code extensible and more readable, which might be a huge load lifted of developers who might have to follow through your work. So with no further delay, let's dig into these pillars of modern day OOP architecture.

#1 Single Responsibility Principle

In simpler terms it means that "A class should have just one reason to change." A class should only be responsible for a single task and it would be even more optimised if the same is hidden completely within that class. The main idea is break down the codebase into smaller functions rather than bringing an enormous amount of functionality under one umbrella. SRP makes your code base extensible so that changing a line of code doesn't make your entire file as red as a traffic signal. Also it can really be a headache to implement changes.

#2 Open/ Closed Principle

Alexander Shvets explains this as "Classes should be open for extension but closed for modification." The objective behind this principle is that any new features should not break the code and should be able to be modified via implementation through new subclasses. These can even include methods which help in overriding the parent class functionalities. This principle is not applicable to bugs in the program, because those will definitely require modifications.

#3 Liskov Substitution Principle

To put this succinctly, "when extending a class, one needs to be able to pass objects of the subclass in place of objects of parent class without breaking the client code." In layman terms, the methods and objects of any child class should be able work in hand with the methods and objects of parent class. This principle needs to be followed where you cant affect the code of people who are accessing your classes so its falls into your hands to make the code extensible for them, even while writing overriding methods.

    This principle states some formal rules to be followed by the child classes:

  • The argument types in the functions of a child class should be more abstract or match those arguments in the functions of the parent class.
  • The return type of the child class should match or be a subtype of the return type of the parent class.
  • The functions of child class shouldn't catch the programmers by surprise by throwing any exceptions which are not witnessed in the parent classes.
  • The child classes shouldn't strengthen pre-conditions. This basically means you cannot narrower the range of values which are accepted by the parent class as inheriting them would make the code throw exception when the older values passed the objects of the child class.
  • The child classes shouldn't weaken post conditions. This implies that you cannot broader the range of values resulting from calling of a method inherited from parent class in your child class as this may leave data running in the system in the background often times.
  • The invariants parts of a parent class must be preserved. This is almost always implied but to state it explicitly, the logical part of these classes should be functioning even after extending. And one should try to create new methods and objects instead of meddling with preexisting ones.
  • A child class shouldn't change the values of the private fields of a parent class. This goes without saying but is possible in some languages and hence is explicitly stated to be avoided.
#4 Interface Design Principle

It basically states that The external classes shouldn't be forced to hinge on functions that are not needed for the particular use case." The interfaces designed shouldn't be bulky and should try to be modular and minimal functionality handed to each method so a the client class shouldn't have to access unnecessary memory. This can also break the client class code sometimes because of a bug in a method thats not even being called in the first place.

#5 Dependency Inversion Principle

Quoting Alexander Shvets , this principle states that "High-Level classes shouldn't depend on Low-Level classes. both should depend on abstractions. Abstractions shouldn't depend on details. Details should depend on abstractions."

Low-level classes are meant for basic crud operations and transferring data over network linking databases. while High- Level classes define methods to to execute low level classes and often hold the core business algorithms.

High level classes should be linked to a single interfaces instead of calling multiple interfaces and code the low-level classes first. As soon as the low level classes execute the fundamental algorithms of the business the dependency is inversed as now the low level classes depend on the high level, which wasn't the case as such earlier.

This principle goes hand in hand with open/close principle as the low-level classes can be extended for different business algorithms without affecting parent classes via extension.