Every wannabe a Developer should know to write more readable, extensible and maintainable programs

Image for post
Image for post

Object oriented programming has brought new paradigm in software development. OOP has enabled developers to design classes which combine the data and its functionality in single unit to deal with sole purpose of its existence.

But, this Object-oriented programming doesn’t prevent confusing, inflexible or unmaintainable programs.

Then comes the Uncle Bob aka Robert C. Martin, with his 5 guidelines/principles. These 5 principles helps developers to create more readable, flexible and maintainable program. SOLID Principles is a coding standard that all developers should have a clear concept for developing software in a proper way to avoid a bad design.

SOLID is a mnemonic acronym for five design principles which individually stands for :

  • S : Single Responsibility Principle
  • O : Open Close Principle
  • L : Liskov Substitution Principle
  • I : Interface Segregation Principle
  • D : Dependency Inversion Principle

Before we start looking at each of the principles individually, let me give you a brief introduction about me and my work. I work as a Developer on an IIOT platform (Field/Edge to Cloud/DC) enabling simplification and acceleration of Real Time data.

I’ll be explaining SOLID principles using telemetry data formats which is used in IOT fields. The data format is nothing but a combination of a Key and a Value.

For example :

Combination of both will create a KvEntry (Key-Value Entry). Based on different data type of Value there are multiple implementation for KvEntry. For Example, DoubleKvEntry, StringKvEntry, BooleanKvEntry, LongKvEntry.

Those who didn’t get the context of KvEntry data, you guys don’t have to worry, I’ll give more example with each principle to make is easy to understand.

Let’s take each principle one by one :

S : Single Responsibility Principle

As the name says :

“A Class should have one and only one job”

A class should be responsible for only one thing. If a class has more than one responsibility than its a violation of the first principle. One job/thing doesn’t mean the class should have only one method. Instead it says all method should relate directly to the responsibility of the class and work towards the same goal.

For example,

Does this KvEntry class violates the Single Responsibility Principle. If yes, then How?

Here we can draw out that KvEntry class have two responsibility : KvEntry properties management and KvEntry database management. The constructor, getKey methods do property management whereas saveKvEntry manages KvEntry storage on database.

In future it will affects the application adversely. As in, if application changes the way it manages database management, then the classes that uses KvEntry class need to touched and recompiled to compensate for the changes.

We can see the rigidity in the application.

To make this conform to Single Responsibility Principle, we create another class that will handle the database management for KvEntry.

With these changes our application will become highly cohesive.

Another example, I will just provide the class that violates the Single Responsibility Principle. Try by yourself and see how it violates the principle and suggest changes.

O : Open Close Principle

In simple words its says :

“Software entities should be open for extension, but closed for modification”

Software entities are like classes, modules, functions, etc.

In much simpler words, it means that a class should be easily extendable without modifying the existing class or function itself.

Let’s continue with our KvEntry class

We want to iterate through the list of KvEntry and print the data type for its values.

As we can see, for every new implementation of KvEntry, we need to add a new logic to printDataType method. For such a small application it’s pretty easy to handle these conditions. But as our application grows and become complex, we will see that the if statements are getting repeated again and again in printDataType method each time a new KvEntry is added.

Now, the question is how to conform Open Close Principle?

KvEntry now has a virtual method getDataType. We have each KvEntry extend the KvEntry class and implement the virtual getDataType method. Here each implementation of KvEntry have is own implementation of data type.

Now, if we add a new data type of KvEntry, the printDataType method doesn’t have to change. All we need to do is to add a new KvEntry in KvEntries arraylist.

Another example to think on, extending our previous Animal class.

L : Liskov Substitution Principle

Its wikipedia definition says :

“Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

Pretty complex right, let me put it in simple words :

“Every subclass/derived class should be substitutable for their base/parent class.”

The principle says that a sub-class can take the place of its super-class without errors. In other words, a subclass should override the parent class methods in a way that it doesn’t break functionality from a client’s point of view.

If the code finds itself checking the type of class then, it must have violated this principle.

Let’s take an example :

The above implementation of printValues violates the Liskov Substitution Principle ( and also the Open Close Principle). It must know of every KvEntry type and call the associated getValue function.

With every new implementation of KvEntry, the printValues method must be modified to accept the new KvEntry.

So to follow the Liskov Substitution Principle there are two rules:

  • If the super-class (KvEntry) has a method that accepts a super-class type (KvEntry) parameter, then is sub-class (LongKvEntry) should accept an argument of super-class type (KvEntry) or sub-class type (LongKvEntry).
  • If the super-class returns a super-class type (KvEntry). Its sub-class should return a super-class type (KvEntry type) or sub-class type(LongKvEntry).

Now, let’s reimplement the printValues method :

The printValues method cares less about the type of KvEntry passed, it just calls the getValue method. All it knows is that the parameter must be of an KvEntry type, either the KvEntry class or its sub-class.

The KvEntry class now have to implement/define a getValue method:

And its sub-classes have to implement the getValue method:

When it’s passed to the printValues function, it returns the long value it has.

We can see that, the printValues doesn’t need to know the type of KvEntry to return its value, it just calls the getValue method of the KvEntry type because by contract a sub-class of KvEntry class must implement the getValue function.

Here is an another problem for you to try implementing Liskov Substitution Principle :

I : Interface Segregation Principle

What wikipedia says :

“many client-specific interfaces are better than one general-purpose interface.”

In more simple words :

“A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.”

This principle mainly deals with the disadvantages of implementing big interfaces.

Let’s take a break from KvEntry example and try an old and typical example of Shape :

This interface draws circle and square. class Circle, Square implementing IShape interface must implement both methods drawCircle, drawSquare.

Doesn’t the above implementation looks weird and somewhat funny. class Circle implements method drawSquare it has no use of, likewise class Square implementing drawCircle.

Now, in a new requirement we need to support a new Shape Triangle.

All classes need to implement the new method otherwise error will be thrown.

We see that it is impossible to implement a shape that can draw a circle but not a square or a triangle. We can just implement the methods to throw an error that shows the operation cannot be performed.

Interface segregation principle frowns against the design of this IShape interface. Clients (here Circle, and Square) should not be forced to depend on methods that they do not need or use. Also, Interface segregation principle states that interfaces should perform only one job (just like the Single Responsibility Principle) any extra grouping of behavior should be abstracted away to another interface.

Here, our IShape interface performs actions that should be handled independently by other interfaces.

To make our IShape interface conform to the Interface Segregation principle, we segregate the actions to different interfaces:

The ICircle interface handles only the drawing of circles, IShape handles drawing of any shape, ISquare handles the drawing of only squares and ITriangle handles the drawing of only triangles.

OR

Classes (Circle, Square, Triangle, etc) can just inherit from the IShape interface and implement their own draw behavior.

We can then use the I-interfaces to create Shape specifics like Semi Circle, Right-Angled Triangle, Equilateral Triangle, Blunt-Edged Rectangle, etc.

D : Dependency Inversion Principle

By Definition it says :

“Entities must depend on abstractions, not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.”

There comes a point in software development where our app will be largely composed of modules. When this happens, we have to clear things up by using dependency injection. High-level components depending on low-level components to function.

By applying the Dependency Inversion the modules can be easily changed by other modules just changing the dependency module and High-level module will not be affected by any changes to the Low-level module.

Let’s take a look at an example :

There’s a common misunderstanding that dependency inversion is simply another way to say dependency injection. However, the two are not the same.

In the above code in spite of Injecting MySQLConnection class in QueryExecutor class but it depends on MySQLConnection. High-level module QueryExecutor should not depend on low-level module MySQLConnection.

If we want to change the connection from MySQLConnection to PostgresDBConnection, we have to change hard-coded constructor injection in QueryExecutor class.

QueryExecutor class should depend upon on Abstractions not on concretions. But How can we do it ?

In the above code, we want to change the connection from MySQLConnection to PostgresDBConnection, we don’t need to change constructor injection in QueryExecutor class. Because here QueryExecutor class depends upon on Abstractions, not on concretions.

Conclusion

So, we have covered all the 5 SOLID principles. In start it will look like some rocket science, but trust me with steady practice and a little patience, these principles will be a part your programs.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store