Firstly, let’s figure out what do you meant by SOLID Principles. SOLID principles are set of five principles that can be used in Object-Oriented programming. It’s a set of best practices / design patterns to follow when you are designing a class/object structure.
One famous quote in software engineering is “It is not enough for code to work” by Robert C Martin, Clean Code. Keeping that in mind let’s evaluate the SOILD principles and how it will help software development in the long run.
When and why we need to apply SOLID?
SOLID principles will help us to lay the foundation on building clean and maintainable architectures.
When you don’t design your application with best practices, you will end up with many issues sooner than later. For examples
- High Code Fragility: Fragility is the tendency of the software to break in many places when every time its changed
- High Code Rigidity: Rigidity is the tendency for software to be difficult to change, even in simple ways
Having fragility and rigidity in your project means having symptoms of tech debt, which is the silent killer of all software projects. In most of the software development process, with the business priority and push towards for quick delivery, engineers tend to prioritize fast delivery over code quality which will tend to introduce technical debt.
Applying SOLID principles help us to control the technical debt. What are those SOLID Principles?
- SRP – Single Responsibility Principle
- OCP – Open Close Principle
- LSP – Liskov Substitution Principle
- ISP – Interface Segregation Principle
- DIP – Dependency Inversion Principle
Let’s take each principle one by one and understand it in very high level so we can apply them in our projects.
Single Responsibility Principle (SRP)
A Class should do only one thing, and therefore it should have only a single reason to change. Some of the common responsibilities in our code are, Business Logic, User Interface, Persistence, Logging and Orchestration.
A common anti-pattern is that, In an Invoice class having the implementation of printInvoice and saveToFile like functions. Ideally those two should be in different classes. Those classes might be InvoicePrinter and InvoicePersistence and those classes will have a single responsibility.
Also avoid common Utils class for having all helper functions. Have specialized classes where you can have them.
Open Close Principle (OCP)
OCP means, Classes should be open for extension and closed to modification. Closed for modification means that each new feature should not modify existing source code. Open for extensions means a component should be extendable to make it behave in new ways.
There are two OCP implementation strategies. One is via Inheritance and other via Strategy pattern. The common usage is to use the strategy pattern to extend the behaviors. For example, having an interface called InvoicePersistence and implementing any number of different classes (DB persistence, File Persistence) based on the main interface. This ensure Invoice Persistence is extendable in many ways.
Liskov Substitution Principle (LSP)
What is Liskov principle? In simple terms, “Any object of a type must be substitutable by objects of a derived typed without altering the existing functionality of that program”. Rather than thinking about the Is-a relationship, just think on Is-substitutable-by in object relationships of your application context and design the objects hierarchy is the key takeaway of LSP. We will observe violations of LSP, when there are partial implemented interfaces in classes (functions which are throwing not implemented exception). Therefore, avoid empty implementations and have proper class hierarchy to avoid such scenarios.
Interface Segregation Principle (ISP)
The key point to note here is that, the term interface is not referring to Java Interface. It can be an interface or abstract class in your application. By definition ISP defined as “Clients should not be forced to depend on methods that they do not use”. Therefore, having small interfaces with a single focus will comply the ISP. By doing so, it will reinforce the LSP and SRP. Key advantage of lean interface is that, minimize dependencies on unused members and reduce code coupling. How to identify that you have “Fat” interface? it’s not much difficult, if you see an interface with large number of method definitions is a symptom for Fat interface. Analyze the interfaces and break it down is the way forward. If you are dealing with legacy code and wanted to refactor the code to adhere the ISP, then you can use the “Adaptor” design pattern to fix the existing issues.
Dependency Inversion Principle (DIP)
This principle mainly captures key two concepts, Dependency Injection (DI) and Inversion of Control (IoC) and how those should be used in our application. Those two concepts are well known concepts, following reference link will explain them in detail. (DI, IoC) Java framework like Spring framework have those two concepts in-build.
This conclude the five principles under SOLID principles. You can find good code samples under the reference section for each principle as well. In addition to SOLID, there are few other ways to keep your architecture clean, for example: constant refactoring, application of design patterns, TDD / Unit testing. When you apply SOLID Principles, you will also get the following as short and long-term benefits.
- Code will be easy to understand and reason about
- Changes are faster and have minimum risk level
- Highly maintainable in the long run
- Cost effective
Decision on applying them in correct scenario is totally up to you!
Reference
https://www.baeldung.com/solid-principles
https://app.pluralsight.com/library/courses/solid-software-design-principles-java/table-of-contents
https://refactoring.guru/design-patterns/adapter