Introduction
To design and develop high-quality software, well–established design principles need to be followed. Those five principles make the acronym S-O-L-I-D. That’s what this article is all about.
I will explain SOLID principles in this article. Let’s start with what the SOLID principle is.
What is SOLID?
SOLID stands for
- S- Single Responsible Principle (SRP).
- O- Open Closed Principle (OSP).
- L- Liskov Substitute Principle (LSP).
- I- Interface Segregation Principle (ISP).
- D- Dependency Inversion Principle (DIP).
Michael Feathers introduced the SOLID acronym in the year 2000. SOLID is a design principle that plays a very important role during Object-Oriented design. Software development becomes easy, reusable, flexible, and maintainable using these design principles.
Robert C Martin has promoted SOLID Principles and now it’s very famous. It has changed the development approach and dominated in software development industries.
Benefits of SOLID
- It makes software design more understandable, flexible, and maintainable.
- Best suitable principle can be used as per project requirement.
- Loosely coupled.
- Parallel Development.
- Testability.
- Code becomes smaller and cleaner
- Maintainability – Large Systems or Growing systems become complicated and difficult to maintain. This Principle helps us to create a maintainable system. A maintainable system is very important in industries.
Single Responsible Principle (SRP)
Robert C Martin's original definition is, "A class should have only one reason to change".
As the SRP name says, each and every module/class should have single responsibility in software, and that responsibility is encapsulated by the class.
If a single class has multiple responsibilities it may cause problems. To avoid these problems we should separate responsibilities in multiple classes or modules.
Let’s start with a simple example. Suppose we have a company that creates new members and logs error messages.
What is the code doing? This code has 2 responsibilities
- Add newly added member in the database
- Logging activities that log errors in a text file.
What will we be doing if we want to log errors in the event viewer or in the database? We have to change the logging code in all the classes including member class. This means member functionality is not changed but still, we have to touch member class because of logging. This violates first principle (Single class or module should have a single responsibility).
Now we will separate these two responsibilities in 2 separate classes,
In the above code, two separate classes (Members and Logger) have their own responsibilities, which solved earlier problems in the code and fulfill the single responsibility Principle.
Open Close Principle (OCP)
Bertrand Meyer defines OCP in 1988, “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
Let’s now discuss on what are the problems this principle solved,
Suppose we want to introduce member type and based on the type we want to give/calculate the discount.
Let’s think, what is the problem with the above code? If we want to add another member type, we have to change the working member class code and here we use the Open close principle.
OCP says a class should be,
- Open for extension – means we need to create a base class and that class will be available for extension. This class should have common functionality.
- Close for Modification – Instead of changing the base class, we will extend the base class and add/modify type-specific coding in the derived class.
Assume that we have two types, if MeberType =1 (IMP Member) and MemberType =2 (VIMP Member) and a base class Members. Let's try to undestand using the below diagram.
As base class (Members) is open for extension, derived classes inherited it and extend their functionality as per requirement. In future if we want to add another type, we have to add another derived class.
Let's see the below code,
In the above code, 1 is Base Class and 2&3 are Derived classes. Derived class extended base class and modified discount as per type.
So in simple words, the Member base class is closed for modification but open for extensions.
Liskov Substitution Principle (LSK)
Barbara Liskov introduced this principle in 1987 in the conference (Data abstraction and hierarchy) hence it is called the Liskov Substitution Principle (LSK). This principle is just an extension of the Open Close principle.
Robert C Martin also defines this principle. His definition of LSK is “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it”.
Before we start on LSK, we should have a basic understanding of polymorphism. The polymorphism rule says that during runtime, the parent/base class object can point to any child/derived class object without any error.
Suppose we have members class example as per below,
In the above example, we have
- Base class member which has method Virtual - Add() method to add a member.
- IMPMember derived class has override - Add() method to add IMPMember.
- VIMPMember derived class has override- Add() method to add VIMPMember.
We will use same class throughout the article now.
Now let’s assume that we need to introduce functionality for Enquiry, which means that anyone can enquire and become a member. A company decided to give a discount to anyone who came for an enquiry, so they can become a member.
Let’s now create a Member class,
In the above class example, we have 2 methods:
- getDiscount
- Add – Which override from parent member.
Now we will try to invoke the members list using base (members) class objects and call add methods. Base class object can point to any child class objects without any issues as per inheritance hierarchy.
The above code throws an error as the Enquiry class doesn’t have an Add method.
Let’s implement the Liskov principle. In this case Liskov principle says parent class object can easily replace child objects. So to implement the Liskov principle here, we will create two different interfaces :
- IDatabase – Content Add method.
- IDiscount – Content getDiscount method.
IDatabase & IDiscount interface implemented to Members, IMPMember, VIMPmember classes. As the inquiry class doesn’t have an Add method only IDiscount interface implemented.
That’s all for LSK, now we can create a list of IDatabase and add relevant classes to it to get a list of all members.
Interface Segregation Principle (ISP)
Robert C Martin's definition of ISP, “Clients should not be forced to depend upon interfaces that they do not use.”
Let’s try to understand with an example, suppose our member's class become so popular and thousands of companies are using it. Now a new company wants to implement members class with new functionality read members.
What will we do in this case? If we will change IDatabase interface and add a new function called ReadMemeber then thousands of existing companies will be impacted which will not use read functionality.
ISP principle helps us to solve this problem,
- Instead of adding a new method in existing members-interfaces, will create a new interface.
- IDatabase 1 – Inherit IDatabase and add a new method ReadMember() method in it.
Now new member would be as per below,
So old company members continue using “IDatabase” interface and new company members can start using the new interface “IDatabase1”.
Dependency Inversion principle (DIP)
Robert C Martin has defined DIP as, “High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions”.
Let’s try to understand this principle with an example,
In the first principle, we have created a logger class that is used in members class. Let’s imagine, now we have a requirement to log details in EvernViwer, Database or in the file. To achieve this we need to create
1. Interface called ‘ILogger’. This interface has a method called Handle.
2. Create three classes FileLogger, EverViewerLogger, EmailLogger implementing ILogger interface.
3. Create Members base class as per below
In the first view, all looks good. Right? But if you see closely, you will notice that we violate single responsibility principle. We are creating logger objects in Members class.
To solve this we will apply DIP here,
Now the client is responsible for injecting logger object and our members class free from IF conditions. We have removed logger dependency and that’s the last principle. We can use different methodologies to remove the dependency, and we will discuss that incoming article.
I hope you enjoy this article and that it helped you to understand SOLID principles.