Why SOLID principles are necessary for any Enterprise Software?
SOLID Principles is a an acronym used as a coding standard.Ā Robert MartinĀ introduced it first. It is used across the object-oriented design family. All developers should haveĀ a clarity for developing software inĀ the right way to avoid a bad design. When appliedĀ appropriately it makes your code easier to read, more extendable and logical. A software with a bad designĀ hasĀ inflexible code. Small changes if introduced to such softwareĀ may break existing code or cause bugs. Design principles and patters prove a lot of significance in any software’s scalability, maintainability. It may take time to understand SOLID principles but they improve code quality and provide component reusability making future integrations easier. ExtendingĀ functionality in your existing appĀ without rewriting code at all tiers will be possible through these principles.
Why many software applications fail due to bad code?
All softwares which are not built with future vision of type of change requests or system scalability will die. Following are design flaws that cause dent in software in the long run:
- Put more stress on Classes by assigning more responsibilities to them. (A lot of functionality not related to a class put together.)
- Forcing the classes to depend on each other. If classes are dependent on each other (in other words tightly coupled), then a change in one will surely affect the other.
- Spreading duplicate and redundant code in the system/application.
- HardcodingĀ url, constants, keys etc. as well as habit of declaring static code everywhere.
The right way to avoid all the above is SOLID way. Lets see whatĀ it actually means:
Ā Ā Ā Ā Ā S: Single Responsibility Principle (SRP)
Ā Ā Ā Ā Ā O: Open closed Principle (OSP)
Ā Ā Ā Ā Ā L: Liskov substitution Principle (LSP)
Ā Ā Ā Ā Ā I: Interface Segregation Principle (ISP)
Ā Ā Ā Ā Ā D: Dependency Inversion Principle (DIP)
Single Responsibility Principle (SRP)
This SOLID PrincipleĀ means “A Class should only have a single responsibility“. All its method should relate to function.Ā Class should only have one reason to change; the more entities in the class, the more difficult itĀ becomes to achieve this. Only change in 1 part of software should be all required to affect the class functionality.Ā So we need to build a structure of software in such a way that classes should cover only oneĀ responsibility or functionality.
To understand this letās consider, We implement the methods for a Company.Ā Now we also want logging, error handling, emailing etc, kind of activities then weĀ mustĀ implement all of these into different classes.
For an instance below I have not followed this rule:
publicĀ classĀ Company { Ā Ā Ā Ā Ā publicĀ voidĀ AddCompany() Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā //Implementation Ā Ā Ā Ā Ā Ā Ā Ā Ā MailMessage mailMessage =Ā newĀ MailMessage(“EMailFrom”,Ā “EMailTo”,Ā “EMailSubject”,Ā “EMailBody”); Ā Ā Ā Ā Ā Ā Ā Ā Ā this.SendEmail(mailMessage); Ā Ā Ā Ā Ā } Ā Ā Ā Ā Ā publicĀ voidĀ DeleteCompany() Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā //Implementation Ā Ā Ā Ā Ā } Ā Ā Ā Ā Ā publicĀ voidĀ SendEmail(MailMessage mailMessage) Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā //Implementation Ā Ā Ā Ā Ā } } |
Now if I want to make my code with Single Responsibility Principle I will create a separate class for employee and Email like below:
publicĀ classĀ Company { publicĀ voidĀ AddCompany() { Ā Ā Ā Ā Ā //Implementation Ā Ā Ā Ā Ā MailMessage mailMessage =Ā newĀ MailMessage(“EMailFrom”,Ā “EMailTo”,Ā “EMailSubject”,Ā “EMailBody”); Ā Ā Ā Ā Ā Emailing emailing =Ā newĀ Emailing(); Ā Ā Ā Ā Ā emailing.SendEmail(mailMessage); } publicĀ voidĀ DeleteCompany() { Ā Ā Ā Ā Ā //Implementation } } publicĀ classĀ Emailing { publicĀ voidĀ SendEmail(MailMessage mailMessage) { Ā Ā Ā Ā Ā //Implementation } } |
Ā If we write the database logic, business logic as well as the display logic in a single class, then our class performs multiple responsibilities. So itĀ gets very difficult to change one responsibility without breaking other functionalities. AlsoĀ we may face difficulties with testing andĀ duplication of logic. What if I want to send an email for an Employee in future requirements ? That’s why we recommend to have separate code for each purpose.
Open closed Principle (OSP)
Open closed Principle is defined as “Software entities such as modules, classes, functions, etc. should be open for extension, but closed for modification“. The first thing is āOpen for extensionā which means new functions, classes, modules can inherit existing code when new requirements are generated and “Closed for modification” which means we are allowing its behavior to be extended without modifying existing source code. In a simple way we can say that we should not change already working functionality, it may affect the entire feature of that module.
Easiest way to implement the Open-Closed Principle
- Ā Ā Ā Ā Ā Use derived classes that inherit from base class
- Ā Ā Ā Ā Ā Allow access to use original classes with interface
- Ā Ā Ā Ā Ā Create new method for new feature so better to us derived class instead of base class
NotĀ following this principleĀ brings problem with existing module and its class maintenance becomes difficult. In addition with every change in existing code we have to test our entire code every time. This also breaks our first principle Single Responsibility Principle.
Example
publicĀ classĀ Event { publicĀ intĀ GetDiscount(intĀ input, DiscountType discountType) { Ā Ā Ā Ā Ā intĀ amount = 0; Ā Ā Ā Ā Ā ifĀ (discountType == DiscountType.TypeOne) Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā amount = input – 20; Ā Ā Ā Ā Ā } Ā Ā Ā Ā Ā ifĀ (discountType == DiscountType.TypeTwo) Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā amount = input – 40; Ā Ā Ā Ā Ā } Ā Ā Ā Ā Ā returnĀ amount; } } |
Here weĀ are checking discount types implementing a business Logic with if else conditions so if in future we have another discount for new events then we need to change the logic for this method.
publicĀ classĀ Event { publicĀ virtualĀ intĀ GetDiscount(intĀ amount) { Ā Ā Ā Ā Ā intĀ regularDiscount = 10; Ā Ā Ā Ā Ā returnĀ amount – regularDiscount; } } publicĀ classĀ DiscountOneĀ : Event { publicĀ overrideĀ intĀ GetDiscount(intĀ input) { Ā Ā Ā Ā Ā returnĀ base.GetDiscount(input) – 20; } } publicĀ classĀ DiscountTwoĀ : Event { publicĀ overrideĀ intĀ GetDiscount(intĀ input) { Ā Ā Ā Ā Ā returnĀ base.GetDiscount(input) – 40; } } |
Using this Open-Closed Principle we can inherit from base class and use custom logic for.Ā Here if we are adding more into existing code, we dont need to worry about other functionalitiesĀ and let it remain as it is.
Liskov substitution Principle (LSP)
This principle tells “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program“. It also defines some guidelines for maintaining inheritor substitution. Passing an objectās inheritor in place of the base class shouldnāt break any existing functionality in the called method. You should be able to substitute all implementations of a given interface with each other.
publicĀ classĀ Program { staticĀ voidĀ Main(string[] args) { Ā Ā Ā Ā Ā A objA =Ā newĀ B(); Ā Ā Ā Ā Ā Console.WriteLine(objA.GetValue()); } } publicĀ classĀ A { publicĀ virtualĀ intĀ GetValue() { Ā Ā Ā Ā Ā returnĀ 10; } } publicĀ classĀ BĀ : A { publicĀ overrideĀ intĀ GetValue() { Ā Ā Ā Ā Ā returnĀ 20; } } |
In this example we have used inherited class A and override method GetValue() so at this point class B cannot be replaced by class A and it returns value from class B. In this case we need to have a generic base class so we can use sub class and it will behave correctly.
publicĀ classĀ Program { staticĀ voidĀ Main(string[] args) { Ā Ā Ā Ā Ā C objA =Ā newĀ A(); Ā Ā Ā Ā Ā C objB =Ā newĀ B(); Ā Ā Ā Ā Ā Console.WriteLine(objA.GetValue()); Ā Ā Ā Ā Ā Console.WriteLine(objB.GetValue()); } } publicĀ abstractĀ classĀ C { publicĀ abstractĀ intĀ GetValue(); } publicĀ classĀ AĀ :C { publicĀ overrideĀ intĀ GetValue() { Ā Ā Ā Ā Ā returnĀ 10; } } publicĀ classĀ BĀ : C { publicĀ overrideĀ intĀ GetValue() { Ā Ā Ā Ā Ā returnĀ 20; } } |
Interface Segregation Principle (ISP)
ThisĀ Principle in SOLIDĀ emphasizes asĀ “Clients should not be forced to implement any methods they donāt use”. Rather than one fat interface, numerous little interfaces are preferred based on groups of methods with each interface serving one sub module. Do not create methods which will not be used or implemented.
If we need to implement the functionality and have a common method implementation from the interface or abstract class but we donāt use all the methods from that then create a separate interface and implement individually.Ā
publicĀ interfaceĀ AppService { voidĀ Insert(); voidĀ Update(); voidĀ Delete(); } publicĀ classĀ EmployeeĀ : AppService { publicĀ voidĀ Insert() { Ā Ā Ā Ā Ā Console.WriteLine(“Created”); } publicĀ voidĀ Update() { Ā Ā Ā Ā Ā Console.WriteLine(“Updated”); } publicĀ voidĀ Delete() { Ā Ā Ā Ā Ā Console.WriteLine(“Deleted”); } } publicĀ classĀ CompanyĀ : AppService { publicĀ voidĀ Insert() { Ā Ā Ā Ā Ā Console.WriteLine(“Created”); } publicĀ voidĀ Update() { Ā Ā Ā Ā Ā Console.WriteLine(“Updated”); } publicĀ voidĀ Delete() { Ā Ā Ā Ā Ā throwĀ newĀ NotImplementedException(); } } |
Here we have implementation of AppService interface into employee and company where we donāt want to implement Delete method for Company then we can use another interface service that only provides Delete method to implement like this,
publicĀ interfaceĀ AppService { voidĀ Insert(); voidĀ Update(); } publicĀ interfaceĀ DeleteService { voidĀ Delete(); } publicĀ classĀ EmployeeĀ : AppService, DeleteService { publicĀ voidĀ Insert() { Ā Ā Ā Ā Ā Console.WriteLine(“Created”); } publicĀ voidĀ Update() { Ā Ā Ā Ā Ā Console.WriteLine(“Updated”); } publicĀ voidĀ Delete() { Ā Ā Ā Ā Ā Console.WriteLine(“Deleted”); } } publicĀ classĀ CompanyĀ : AppService { publicĀ voidĀ Insert() { Ā Ā Ā Ā Ā Console.WriteLine(“Created”); } publicĀ voidĀ Update() { Ā Ā Ā Ā Ā Console.WriteLine(“Updated”); } } |
Ā So as perĀ SOLID we can only implement required services and no need to write invalid code.Ā We have split that big interface into three small interfaces. Each interface now has some specific purpose.
Dependency Inversion Principle (DIP)
The Dependency Inversion Principle says that “Program to an interface, not to an implementation“. It means high-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions. Secondly, abstractions should not depend upon details.Ā Dependency Inversion means to depend on abstractions instead of concrete types.
If one class knows implementation of another class then it will raise the risk of changing one will affect another. So it should both depend on abstraction. As a result we must keep these high-level and low-level modules/classes loosely coupled as much as possible.
publicĀ classĀ EmployeeController { BusinessLogic service; publicĀ EmployeeController(BusinessLogic _service) { Ā Ā Ā Ā Ā service = _service; } publicĀ List<Employee> GetAllEmployee() { Ā Ā Ā Ā Ā returnĀ service.GetEmployees(); } } publicĀ classĀ BusinessLogic { publicĀ List<Employee> GetEmployees() { Ā Ā Ā Ā Ā //Implementation } } |
Here we are using BusinessLogic class which gives access to data layer.Ā It also givesĀ implementation of actual logic of employee where EmployeeController is higher level class and BusinessLogic is lower level class.Ā Therefore, class/module should not depend on the concrete EmployeeDataAccess class/module, instead, both the classes should depend on abstraction or interface. For instance as below:
publicĀ classĀ EmployeeController { Ā Ā Ā Ā Ā IBusinessLogic service; Ā Ā Ā Ā Ā publicĀ EmployeeController(IBusinessLogic _service) Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā service = _service; Ā Ā Ā Ā Ā } Ā Ā Ā Ā Ā publicĀ List<Employee> GetAllEmployee() Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā returnĀ service.GetEmployees(); Ā Ā Ā Ā Ā } } publicĀ interfaceĀ IBusinessLogic { Ā Ā Ā Ā Ā List<Employee> GetEmployees(); } publicĀ classĀ BusinessLogic: IBusinessLogic { Ā Ā Ā Ā Ā publicĀ List<Employee> GetEmployees() Ā Ā Ā Ā Ā { Ā Ā Ā Ā Ā Ā Ā Ā Ā //Implementation Ā Ā Ā Ā Ā } } |
Now both EmployeeController and BusinessLogic classes are loosely coupled so it doesnāt depend on concreteĀ BusinessLogic class.
Conclusion
Indeed, the SOLID principles are generally overlapping in their effects upon sustainable computer code. As the process of writing software has always evolved from the theoretical realm into a true engineering discipline.
HopeĀ I gave you a good picture about how to implement SOLID principles. In case you are looking for additional consulting, or you need to Hire a Developer then we will be happy to help you. For queries, mail your problem or requirement at shalin@ncoresoft.com or skype me @ shalin.jirawla