Understanding SOLID Principles
The SOLID principles are five design principles intended to make object-oriented software more understandable, flexible, and maintainable.
Let's understand what each character in the SOLID Principles means:
S: Single Responsibility Principle
Meaning: Every class should have only one reason to change
Example: If in an OrderService we perform tasks like placing the order, sending email notifications and processing payments, then it would be a bad design. But why?
Let's say we need to make some changes in the email notification logic. Then, in such a situation, we do not need to make any changes to the order service class. But since we have written the email notification logic in the Order service class, we need to make changes. But how can we fix it?
We can split the Order Service class into classes like:
O: Open for extension and closed for modification
Meaning: One should be able to add new functionality without altering existing code
Example: If in a PaymentService class, we have written the logic for making payment through PhonePe and Google Pay through the two methods processPaymentThroughPhonePe() and processPaymentThroughGooglePay(). Now this is a bad design. But why?
Let's say in future we need to add PayPal payment, then we have to write the logic in the PaymentService class, which is violating the open and closed principle. But how to fix?
We can create an iPaymentService interface and declare the processPayment method. We will then create PhonePePaymentService and GooglePayPaymentService, which is going to implement the IPaymentService. Now, if in future we get a new requirement like PayPal payment, then we can simply create a PayPalPaymentService, which is going to implement the iPaymentService and write its own logic in the processPayment method.
L: Liskov Substitution Principle
Meaning: Derived classes should extend base classes without changing their behaviour
Recommended by LinkedIn
Example: Let's say in a UserService class we declare the following methods: viewDashboard(), updateProfile() and manageUsers(). Now, if the user is an Admin, then all these methods make sense, but if the User is an ordinary User, then it should not override the manageUsers() method. Now, since the OrdinaryUser will extend the UserService interface, if we try to access the manageUsers() method from the OrdinaryUser class, then it will cause an error. This will violate the above principle. But how to fix it?
To fix it, we can declare all the generic methods like viewDashboard() and updateProfile() in the UserService class. We can create another AdminService, which will contain only the manageUsers() method. Now, this AdminService is going to extend the UserService. This will solve the problem as the OrdinaryUser can extend UserService while the Admin will extend the AdminService.
I: Interface Segregation Principle
Meaning: Clients should not be forced to depend on methods they do not use.
Example: We can consider the example in the Liskov Substitution Principle. In the example, the OrdinaryUser is forced to implement the logic of manageUsers(), else it will cause an error while trying to access the manageUsers() method through the OrdinaryUser object. This will violate the above Principle as the OrdinaryUser class is dependent on the manageUsers() method even though it does not want to use it.
To fix it, we can create two interfaces like BasicUserActions and AdminUserActions. BasicUserActions will contain the viewDashboard() and updateProfile() methods, while the AdminUserActions will contain manageUsers(). Through this, the OrdinaryUser class can only implement the BasicUserActions, and hence it will not be forced to implement the manageUsers() method. Admin will implement both BasicUserActions and AdminUserActions.
D: Dependency Inversion Principle
Meaning: High-level modules shouldn’t depend on low-level modules. Both should depend on abstractions.
Example: If we try to create the object of PaymentService through its concrete implementation, i.e IPaymentService paymentService = new GooglePay(), then it will violate the above principle. But why ?
If in future we are told to use PhonePe instead of GooglePay then we need to create the object of PhonePe.
To fix this, we can let the client decide which payment service they want to use. We can simply declare the reference of IPaymentService and then set its value through the constructor by passing IPaymentService as a parameter.
Follow Avishake M. for more such content
#systemdesign #article #solidprinciples #designpatterns
Avishake, that is really extensive and well written. 👏👏