SOLID Programming Principles in OOPS

In any object oriented programming language, classes are the main building blocks of the application. In order to make the application robust it should be easily modifiable or extendable with future enhancements and these building blocks (classes) should be well written and easily maintainable. In case these building blocks are not well written lot of problems with respect to design aspects will surface later in the design phase where it will be difficult to handle the issues.

In order to overcome these problems associated with the classes in any object oriented programming languages, there are 5 standard set of principles that can be followed. Implementing these set of standard principles will definitely enhance the coding process and will lead to lesser bugs in the production. These 5 standard principles are termed as SOLID. Where each letter stands for:

The above SOLID principles are also part of the standard and best programming practices in any object oriented programming languages.

This SOLID programming principles were introduced by Michael Feathers which was initially derived by Robert Martin in early 2000 which was known as “First Five Principles”. By applying the above principles by any programmer will definitely make sure that the system developed will be easy to maintain and can be easily extended in future for any design changes.

Let us see each principle in detail with a small example:

Single Responsibility Principle (SRP)

Example:

Consider the below sample Student class, which has the provision to display the student name, register no. and course he/she is pursuing in a college. But, observe that there is one more method save() which saves the student details into the database.

image1

In the above example whenever there is a change needs to be made on the save() method, then this class needs to be modified. Hence, moving this save method to a new class will ensure that the responsibilities are separated and it will be easy to modify the save method without affecting the Student class.

image2

SRP leads to a loosely coupled design with less and lighter dependencies.

Open Closed Principle (OCP)

Example:
Consider a simple insurance claim and approval process that happens in general. In the below example the class HealthInsurance.java is a type of insurance which needs to be processed for the approval and claim. Another class InsuranceClaimApprover.java, which process, validates and approves claim for the reimbursement.

image3

image4

In this InsuranceClaimApprover.java, the design will work fine till the method processHealthClaim() receives the object of the type HealthInsurance which is of the concrete one. But, when a new type of claim for e.g. Vehicle insurance needs to be added, creating a VehicleInsurance.java does not create any problem. But, the class InsuranceClaimApprover needs to be modified to consider the VehiclInsurance as well.

image5

This is how the modified InsuranceClaimApprover looks like:

image6

Apparently this is clearly violating the open closed principle. Because, whenever a new type of insurance needs to be included as part of the customer demands, we need to keep modifying the InsuranceClaimAprrover class which is not to be done.

Hence, to achieve the OCP, a new layer of abstraction needs to be introduced as shown below:

Introduce an abstract class called Insurance:

image7

Modified HealthInsurance and VehicleInsurance classes:

image8

image9

Modified InsuranceClaimApprover.java

image10

 

 

 

This OCP should be applied to those areas of the modules which are most likely to be changed.

Liskov’s Substitution Principle (LSP)

Example:

Consider an interface IProcessData which contains 2 methods loadData() and persistData(). Assume 2 classes TravelerUserProcessData and AdminUserProcessData implements this interface and of course things will work perfectly fine.

image11

image12

image13

Everything will work perfectly fine till a new class is introduced where the implementation for persistData() methods throws a CustomException. Assume there is a class which has a loadAll() method, used to load all the classes along with the other classes. When the application is run, all the classes will be loaded properly except one class SuperAdminUserProcessData which throws the CustomException.

image14

Looking at this, the app may blow up when tried to save all the classes with their details, which in turns concludes that with this design “the object should be substitutable by its base class” but, which is not happening in this case.

Hence, the fix here is to modify the interface based on what each client requirement. i.e. the best approach is to put the methods loadData() and persistData() into 2 different interfaces.

image15

image16

Interface Segregation Principle

Example:

Consider an interface called IDigitalPrinter which has the methods like print, scan and xerox. Whichever the class implements this interface should be provide the implementations to the methods of this interface. Consider a client, who asks for the functionality of print. So, what do be done with the other 2 methods. It is as good as useful for nothing.

image17

image18

So, clients should not be forced rely on the interfaces which they do not require at all. Hence, it is always a better and good practice to breakdown the interfaces into multiple special purpose interfaces as shown below.

image19

image20

image21

Since, the interface is now broken down into multiple special interfaces, client will be provided an option to choose which features to be implemented.

image22

Hence, interface segregation helps implementation to be kept simple and makes the code very much easy to debug and maintain.


Dependency Inversion Principle

Example:

Consider a class called Employee with a method work() as shown below:

image23

Another class Manager manages the employee object

image24

Now consider that the manager class will be having a quite complex logic. Assume a new worker “Super Worker” needs to be introduced by the Manager class.

image25

Now, in order to include the super worker class in the Manager class, this SuperWorker needs to be defined in the Manager class which will make the Manager class to become more complex as the logic grows. And also, whenever a new type of worker in introduced, the Manager class also needs to be changed which is not advisable. Also, here the high level classes depends on the low level class which is again results in bad design as the classes should depend on abstraction and not concretion !!

In order to make the design efficient, have an interface which will be implemented by the different types of worker classes. With this whenever a new type of worker is introduced, the high level class i.e. Manager class will not be modified!!

image26

image27
image28

Modified Manager class

image29

With this approach, the Manager class will be unchanged whenever a new type of worker is introduced as the dependency is now on abstraction not with the concretion!

Leave a Reply

Your email address will not be published. Required fields are marked *