SOLID Principles
- S → Single Responsibility Principle
- O → Open/Closed Principle
- L → Liskov Substitution Principle
- I → Interface Segregation Principle
- D → Dependency Inversion Principle
Single Responsibility Principle
Section titled “Single Responsibility Principle”A class should always have one responsibility and there should be only a single reason to change it.
- Goal: To create highly cohesive classes that are easier to understand, test, and maintain.
Bad Implementation
- Below Employee class contains personal details, business logic to perform a few calculations, and DB logic to save/update.
- Our class is tightly coupled, hard to maintain, multiple reasons to modify this class.

Good Implementation
- We can split a single Employee class into multiple classes as per their specific responsibility. It made our class loosely coupled, easy to maintain, and only single reason to modify.


Open/Closed Principle
Section titled “Open/Closed Principle”Class should be Open for Extension but Closed for Modification.
- Goal: To allow adding new functionality without modifying existing code, reducing the risk of introducing bugs.
Bad Implementation
- Below EmployeeSalary class calculates salary based on employee type: Permanent and Contractual.
- Issue: In the future, if a new type (Part-time Employee) comes then the code needs to be modified to calculate the salary based on employee type.

Good Implementation
- We can introduce a new interface EmployeeSalary and create two child classes for Permanent and Contractual Employees.
- By doing this, when a new type comes then a new child class needs to be created and our core logic will also not change from this.


Liskov Substitution Principle
Section titled “Liskov Substitution Principle”Child Classes should be replaceable with Parent Classes without breaking the behavior of our code.
- Goal: To ensure that derived classes can be used in place of their base classes without causing unexpected behavior.
Bad Implementation
- Below, TeslaToyCar extends Car but does not support fuel() method as its toy. That’s why it’s violating the LS principle.
- In our code where ever we’ve used Car, we can’t substitute it directly with TeslaToyCar because fuel() will throw Exception.

Good Implementation
- Creating new subclass RealCar from parent Car class, so that RealCar can support fuel() and Car can support generic functions support by any type of car.
- As shown below, TeslaToyCar and TeslaRealCar can be substituted with their respective Parent class.


Interface Segregation Principle
Section titled “Interface Segregation Principle”- Interface should only have methods that are applicable to all child classes.
- If an interface contains a method applicable to some child classes then we need to force the rest to provide dummy implementation.
- Goal: To avoid creating large, monolithic interfaces that force classes to implement methods they don’t need.
Bad Implementation
- Vehicle interface contains the fly() method which is not supported by all vehicles i.e. Bus, Car, etc. Hence they’ve to forcefully provide a dummy implementation.
- It violates the Interface Segregation principle as shown below:

Good Implementation
- Pulling out fly() method into new Flyable interface solves the issue. Now, Vehicle interface contains methods supported by all Vehicles.
- And, Aeroplane implements both Vehicle and Flyable interface as it can fly too.


Dependency Inversion Principle
Section titled “Dependency Inversion Principle”- Class should depend on abstractions (interface and abstract class) instead of concrete implementations.
- If implementation changes then the class referring to it via abstraction won’t change.
- Goal: To reduce coupling between classes by making them depend on abstractions rather than concrete implementations.
Bad Implementation
- We’ve got a Service class, in which we’ve directly referenced concrete class (SQLRepository).
- Issue: Our class is now tightly coupled with SQLRepository, in future if we need to start supporting NoSQLRepository then we need to change Service class.

Good Implementation
- Create a parent interface Repository and SQL and NoSQL Repository implements it. Service class refers to Repository interface, in future if we need to support NoSQL then simply need to pass its instance in constructor without changing Service class.

