Skip to main content

Advantages Of Using The Single Responsibility Principle

Easier Maintenance

When a class or module is only responsible for one aspect of system functionality, it's much easier to understand, maintain, and update. Changes for a specific feature or in response to a change in requirements should only affect a single class. You don't have to worry about a modification rippling out to other unrelated sections of your code.

User Class Before applying SRP
class User {
name: string;
email: string;

constructor(name: string, email: string) {
this.name = name;
this.email = email;
}

saveUserToDB() {
// Implementation here
}

sendWelcomeEmail() {
// Implementation here
}
}

In the above example, the User class handles both database operations and email operations. If there's a change in the database structure or email system, the User class would need to be updated.

User Class After applying SRP
class User {
name: string;
email: string;

constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}

class UserDB {
saveUser(user: User) {
// Implementation here
}
}

class EmailService {
sendWelcomeEmail(user: User) {
// Implementation here
}
}

Now, the User class, UserDB class, and EmailService class each have a single responsibility. If there are changes to how we interact with the database or email system, we only need to update UserDB or EmailService respectively. This makes the system easier to maintain.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

Improved Understandability

Code that follows SRP tends to be more readable and understandable. Each class has a single focus, and its purpose is generally clear to developers. This saves a lot of time in code comprehension, which is a big part of software development.

User Class After applying SRP
class User {
name: string;
email: string;

constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}

class UserDB {
saveUser(user: User) {
// Implementation here
}
}

class EmailService {
sendWelcomeEmail(user: User) {
// Implementation here
}
}

By splitting large classes into smaller ones each with a single responsibility, the purpose of each class becomes more apparent. In the previous example, it's immediately clear that User represents a user, UserDB handles database interactions for users, and EmailService handles email operations.

Easier Testing

It's generally easier to write unit tests for code that follows SRP. Each unit test should correspond to a single functionality or behaviour. If a class has a single responsibility, you can write focused tests that check whether that responsibility is being fulfilled correctly. In contrast, a class that handles multiple responsibilities can have complex interdependencies that make it harder to test.

With SRP, we can write more focused tests. Here's how you might test the UserDB and EmailService classes:

Classes become easier to test
// Testing UserDB
describe("UserDB", () => {
it("should save user to the database", () => {
const userDB = new UserDB();
const user = new User("John", "john@example.com");
// Your test implementation here
});
});

// Testing EmailService
describe("EmailService", () => {
it("should send a welcome email to the user", () => {
const emailService = new EmailService();
const user = new User("John", "john@example.com");
// Your test implementation here
});
});

Reduced Coupling

The SRP helps reduce coupling between different parts of a system. If a class or module takes on too many responsibilities, changes to one area can inadvertently affect another, creating a fragile system where small changes can have large, unpredictable effects. By ensuring that each class has only one reason to change, you minimize the impact of changes and reduce the risk of creating bugs in unrelated features.

Let's consider an example using a Book class that includes methods for both data management (CRUD operations) and data presentation (generating a HTML display of book data). This design violates the Single Responsibility Principle (SRP) and can lead to high coupling.

Book Class Before applying SRP
class Book {
title: string;
author: string;

constructor(title: string, author: string) {
this.title = title;
this.author = author;
}

// Methods related to data management
createBook() {
// Implementation here
}

readBook() {
// Implementation here
}

updateBook() {
// Implementation here
}

deleteBook() {
// Implementation here
}

// Method related to data presentation
displayHTML() {
return `<h1>${this.title}</h1><p>${this.author}</p>`;
}
}

In this scenario, changes to how books are stored or retrieved from a database might affect the way books are displayed in HTML, and vice versa. This high coupling can lead to unintended consequences and bugs.

Book Class After applying SRP
class Book {
title: string;
author: string;

constructor(title: string, author: string) {
this.title = title;
this.author = author;
}

// Methods related to data management
createBook() {
// Implementation here
}

readBook() {
// Implementation here
}

updateBook() {
// Implementation here
}

deleteBook() {
// Implementation here
}
}

class BookPresenter {
book: Book;

constructor(book: Book) {
this.book = book;
}

displayHTML() {
return `<h1>${this.book.title}</h1><p>${this.book.author}</p>`;
}
}

In this refactored code, the Book class is only responsible for data management, while the BookPresenter class is responsible for displaying the book data in HTML. The two classes are decoupled, so changes in one won't affect the other. For example, you can change how books are displayed in HTML without worrying about how these changes might affect data management. This decoupling makes the code easier to maintain and less prone to bugs.

Increased Reusability

Classes that do one thing and do it well are more likely to be reusable in different contexts. If a class combines multiple responsibilities, it's less likely to fit neatly into a new context where only some of its functionality is required. Following SRP can help you build a library of highly reusable components, increasing your codebase's flexibility and efficiency.

In the refactored Book example, we have two separate classes: Book for managing book data and BookPresenter for presenting book data in HTML format.

Now, consider a situation where you want to present book data not just in HTML, but also in plain text or JSON format for different purposes, like for a plaintext email or an API response. With the SRP applied, it's easy to add new presenter classes without modifying the Book class or the BookPresenter class. Here's how you could do that:

class TextBookPresenter {
book: Book;

constructor(book: Book) {
this.book = book;
}

displayText() {
return `Title: ${this.book.title}, Author: ${this.book.author}`;
}
}

class JSONBookPresenter {
book: Book;

constructor(book: Book) {
this.book = book;
}

displayJSON() {
return JSON.stringify({ title: this.book.title, author: this.book.author });
}
}

This way, Book can be reused across different contexts (HTML display, text display, JSON display) without any modification to its code. That's the power of reusability that comes from adhering to the Single Responsibility Principle. By keeping classes focused on one task, we can extend functionality without modifying existing classes, which is in alignment with the Open/Closed Principle, another principle of SOLID.

On the other hand, in the original Book class before applying SRP, if you wanted to add a feature to display books in text or JSON format, you would have to modify the Book class itself, potentially disrupting its existing functionality. The original Book class is less reusable because its responsibilities are too diverse, making it harder to extend its functionality without introducing new dependencies and potential bugs.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.

YouTube @cloudaffle