Last updated on May 16th, 2024 at 11:14 pm
Introduction: Why Embrace SOLID Principles?
In the software development, the SOLID principles are essential for creating scalable, maintenance-friendly, and sturdy applications. Robert C. Martin coined these principles to guide developers in building adaptable software that meets new requirements without disrupting existing functionality. For applications that require frequent rapid changes to meet customer demands, SOLID can be truly transformative.
Here is the complete blog article on SOLID principles with a focus on a use case of an notification application, structured according to your request:
The Initial Challenge: Notification Application Without SOLID
Consider a typical scenario in an e-commerce application responsible for handling various types of user notifications such as email, SMS, and push notifications. Initially, we have a NotificationManager class that violates multiple SOLID principles:
class NotificationManager {
sendEmail(message) { console.log(`Sending email: ${message}`); }
sendSMS(message) { console.log(`Sending SMS: ${message}`); }
sendPush(message) { console.log(`Sending push notification: ${message}`); }
notify(user, message, type) {
switch (type) {
case 'email': this.sendEmail(message); break;
case 'sms': this.sendSMS(message); break;
case 'push': this.sendPush(message); break;
default: throw new Error("Unsupported notification type.");
}
}
}
Single Responsibility Principle (SRP)
Problem Identification
The NotificationManager is overloaded with responsibilities, including sending emails, SMS, and push notifications. This has made the class unwieldy and difficult to maintain. As the codebase grows, complex and convoluted logic within the class may make it challenging to debug and enhance functionality. It’s imperative to consider a more streamlined and modular approach for a more maintainable and scalable system.
Refactoring Solution:
Split the responsibilities into separate classes, ensuring that each class has only one reason to change:
class EmailNotifier {
send(message) { console.log(`Sending email: ${message}`); }
}
class SMSNotifier {
send(message) { console.log(`Sending SMS: ${message}`); }
}
class PushNotifier {
send(message) { console.log(`Sending push notification: ${message}`); }
}
Open/Closed Principle (OCP)
Problem Identified: The original class required modifications whenever new notification types were added.
The original class presented an identified issue by necessitating modifications whenever new notification types were incorporated. This inefficiency not only increased development time but also introduced the possibility of errors or oversights in the codebase. As a consequence, it is imperative to adopt a more scalable and adaptable approach to ensure the seamless integration of new notification types without the need for extensive modifications to the existing class structure.
Refactoring Solution:
Utilize a base class that can be extended for different notification types without modifying existing code:
By integrating a base class to extend for email, SMS, or push notifications, developers ensure a robust, flexible codebase. This approach allows adding new notification types without modifying existing code, promoting scalability and maintainability. A base class simplifies adding new functionality, reducing the risk of introducing bugs or disrupting the existing system.
class Notifier {
send(message) { throw new Error("Method 'send' must be implemented."); }
}
class EmailNotifier extends Notifier {
send(message) { console.log(`Sending email: ${message}`); }
}
class SMSNotifier extends Notifier {
send(message) { console.log(`Sending SMS: ${message}`); }
}
// New notification type
class SlackNotifier extends Notifier {
send(message) { console.log(`Sending Slack message: ${message}`); }
}
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) was introduced by Barbara Liskov in 1987. It dictates that a subclass should seamlessly replace its superclass without affecting the program. This principle maintains the original class’s behavior when using a subclass. It ensures flexibility and robustness in object-oriented systems.
Therefore, the refactoring solution should ensure that all subclasses of the Notifier class can seamlessly replace the Notifier itself without affecting the program’s intended behavior and functionality. This maintains flexibility and extensibility, allowing for new Notifier subclasses to be added or existing ones to be modified without adverse effects on the system. Adhering to this refactoring principle makes the codebase more adaptable to future changes, facilitating efficient maintenance and development processes.
function sendNotification(notifier, message) {
notifier.send(message);
}
Interface Segregation Principle (ISP)
The Interface Segregation Principle (ISP) stresses designing specific, client-tailored interfaces over broad ones. In the notification app, ISP is employed by ensuring each notifier class implements a single `Notifier` interface method, `send`, aligned with its purpose. This approach avoids needlessly implementing unused methods, promoting cohesive and relevant interfaces. Adhering to ISP achieves a more modular and precise structure, aligning with SOLID principles to enhance software systems’ maintainability, scalability, and adaptability.
Dependency Inversion Principle (DIP)
The Dependency Inversion Principle (DIP) is crucial. It’s part of the SOLID principles in OOP. It’s seen in the notification application. It shows by separating high-level modules from specific concrete implementations. For example, NotificationService and EmailNotifier, SMSNotifier, PushNotifier, and SlackNotifier. Instead of depending directly on these concrete classes, NotificationService now relies on the abstraction provided by the Notifier class.
Refactoring Solution:
class NotificationService {
constructor(notifier) { this.notifier = notifier; }
sendAlert(message) { this.notifier.send(message); }
}
This restructuring facilitates greater flexibility and ease of maintenance, as the abstraction can be implemented by various concrete classes, resulting in a more modular and adaptable system. By adhering to the Dependency Inversion Principle, the codebase achieves a level of abstraction and decoupling that promotes scalability, maintainability, and extensibility of the software system.
Final Output
After applying all SOLID principles, our notification service application now features modular, extendable, and maintainable notification handling. New types of notifications can be added with minimal changes, adhering to all five principles effectively.
Base Notifier Class:
class Notifier {
send(message) {
throw new Error("Method 'send' must be implemented.");
}
}
Concrete Notifier Subclasses:
class EmailNotifier extends Notifier {
send(message) {
console.log(`Sending email: ${message}`);
}
}
class SMSNotifier extends Notifier {
send(message) {
console.log(`Sending SMS: ${message}`);
}
}
class PushNotifier extends Notifier {
send(message) {
console.log(`Sending push notification: ${message}`);
}
}
class SlackNotifier extends Notifier {
send(message) {
console.log(`Sending Slack message: ${message}`);
}
}
Notification Service:
class NotificationService {
constructor(notifier) {
this.notifier = notifier;
}
sendAlert(message) {
this.notifier.send(message);
}
}
Using the Notification Service:
// Example usage
const emailService = new NotificationService(new EmailNotifier());
const smsService = new NotificationService(new SMSNotifier());
const pushService = new NotificationService(new PushNotifier());
const slackService = new NotificationService(new SlackNotifier());
emailService.sendAlert("50% off on all electronics!");
smsService.sendAlert("Your order has been shipped.");
pushService.sendAlert("New in-app exclusive deals available!");
slackService.sendAlert("Reminder: Team meeting at 3 PM today.");
Description of Final Output
- Modularity: Each notification type is managed by its own subclass of
Notifier, ensuring that each class has a single responsibility and is open for extension but closed for modification. - Flexibility: New notification types (like
SlackNotifier) can be added without modifying existing code, demonstrating the Open/Closed Principle. - Substitutability: All notifier types can be used interchangeably in the
NotificationService, adhering to the Liskov Substitution Principle. - Abstraction: The
NotificationServicedepends on the abstract classNotifier, not on concrete implementations, aligning with the Dependency Inversion Principle.
Conclusion
The application of SOLID principles transforms the design and architecture of software systems. By embracing these principles, developers can ensure that their applications are not only robust and flexible but also ready for future expansion and integration. This leads to code that is easier to understand, test, and maintain, paving the way for sustained innovation and success.
In the following blogs, we will get into the details of each SOLID principle
1. How to Correctly Apply the Single Responsibility Principle (SRP) for Better Code
2. Understanding Open/Closed Principle (OCP) for Scalable Solutions
References:
Here are some valuable resources that provide in-depth knowledge about SOLID principles:
- “Clean Architecture: A Craftsman’s Guide to Software Structure and Design” by Robert C. Martin – This book gives detailed insights into SOLID principles and their practical applications.
Buy on Amazon - Refactoring.Guru – This website is excellent for learning about design patterns and principles, including SRP, with clear explanations and examples.
Visit Refactoring.Guru on SRP - “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin – This book is crucial for developers looking to improve their coding practices and embrace SRP.
Buy on Amazon - Martin Fowler’s Blog – A resource rich with articles on software architecture, including discussions on the responsibilities of software components.
Explore Martin Fowler’s Blog
Discover more from kumarvinay.com
Subscribe to get the latest posts sent to your email.


Nice Article.
Pingback: How to Correctly Apply the Single Responsibility Principle (SRP) for Better Code | kumarvinay.com
Pingback: How to apply Open/Closed Principle (OCP) correctly | kumarvinay.com