Classes are crucial in object-oriented programming but poorly designed ones can lead to problematic code.
Clean Code Chapter 10 highlights the importance of cohesive classes with a single responsibility.
In this article, we'll share key insights and demonstrate their application in JavaScript.
📌 What is Class Cohesion?
Cohesion refers to how closely related the responsibilities of a class are.
A cohesive class focuses on a single purpose and has responsibilities that naturally fit together.
This keeps the class simple, readable, and easy to maintain.
Example: Low Cohesion
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Handles user registration
register() {
// Registration logic
}
// Sends email to user
sendEmail(message) {
// Email sending logic
}
// Logs activity of the user
logActivity(activity) {
console.log(`${this.name} performed: ${activity}`);
}
}
In the example above, the User
class has three unrelated responsibilities: user registration, sending emails, and logging activity.
This class lacks cohesion because it tries to do too many things simultaneously.
📌 The Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have one, and only one, reason to change. This means that each class should focus on a single concern.
If a class has more than one responsibility, changes in one area could break the functionality of another.
⚡️ Refactoring for SRP
Let’s refactor the above example to adhere to SRP:
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRegistrationService {
register(user) {
// Registration logic
}
}
class EmailService {
sendEmail(user, message) {
// Email sending logic
}
}
class ActivityLogger {
logActivity(user, activity) {
console.log(`${user.name} performed: ${activity}`);
}
}
Now, each class has a single responsibility:
-
User:
represents the user object and holds data. -
UserRegistrationService:
handles user registration. -
EmailService:
handles sending emails. -
ActivityLogger:
handles logging user activity.
With this structure, changes to the email system won't affect the user registration logic, and vice versa.
📌 Benefits of SRP and Cohesion
Maintainability: When a class has a single responsibility, it's easier to locate and fix bugs. You don't need to wade through unrelated logic.
Scalability: As your project grows, adhering to SRP makes it simpler to add new features. New functionalities can be added in new classes without touching existing ones.
Testability: Classes with a single responsibility are easier to test. Each class has a limited scope, so unit tests can focus on individual pieces of functionality.
📌 Identifying Responsibility Overlap
To ensure that your classes are cohesive, look for areas where multiple responsibilities are being combined.
Often, a class starts simple, but as features are added, it can accumulate extra duties.
Example: Refactoring a Payment System
Let’s say we have a PaymentProcessor
class that handles multiple tasks:
class PaymentProcessor {
constructor() {
this.paymentGateway = new PaymentGateway();
}
processPayment(paymentDetails) {
// Payment processing logic
}
validatePaymentDetails(paymentDetails) {
// Validation logic
}
logTransaction(paymentDetails) {
console.log(`Payment processed: ${JSON.stringify(paymentDetails)}`);
}
}
Here, PaymentProcessor
is responsible for processing payments, validating details, and logging transactions.
This is an opportunity to refactor and split responsibilities:
class PaymentProcessor {
constructor(paymentGateway, validator, logger) {
this.paymentGateway = paymentGateway;
this.validator = validator;
this.logger = logger;
}
processPayment(paymentDetails) {
if (this.validator.isValid(paymentDetails)) {
this.paymentGateway.process(paymentDetails);
this.logger.logTransaction(paymentDetails);
}
}
}
class PaymentValidator {
isValid(paymentDetails) {
// Validation logic
return true; // Simplified for example
}
}
class PaymentLogger {
logTransaction(paymentDetails) {
console.log(`Payment processed: ${JSON.stringify(paymentDetails)}`);
}
}
Now, the PaymentProcessor
class has a single responsibility: processing payments.
Validation and logging have been moved to separate classes (PaymentValidator
and PaymentLogger
).
⚡️ Conclusion
Designing classes with cohesion and adhering to the Single Responsibility Principle ensures your codebase remains flexible, maintainable, and easy to understand.
By splitting responsibilities into separate, focused classes, you reduce the complexity of individual components and make your system more robust.
In JavaScript, where classes are often used in larger frameworks or applications, following these principles will significantly improve the quality of your code.
As the Clean Code book explains, writing clean classes isn’t just about organizing code—it’s about creating a system that can evolve without becoming a nightmare to maintain.
Happy Coding!
Top comments (0)