When designing software systems, the concept of cohesion plays a crucial role in determining the quality and maintainability of your code. Cohesion refers to how closely related the responsibilities of a single module or class are. Understanding the difference between high cohesion and low cohesion is essential for writing clean, robust, and scalable code.
In this blog post, we’ll explore what cohesion means, compare high and low cohesion, and provide practical examples to illustrate these principles.
What is Cohesion?
Cohesion measures how well the parts of a module or class fit together to achieve a single purpose. It reflects the degree to which the responsibilities of a class or module are focused and aligned.
Types of Cohesion:
- High Cohesion:
- A module or class has a clear, well-defined responsibility.
- All methods and properties work toward a single, unified goal.
- Low Cohesion:
- A module or class is responsible for unrelated tasks.
- The code becomes harder to understand, maintain, and test.
High Cohesion
Characteristics of High Cohesion:
- Each module or class focuses on a single responsibility.
- Follows the Single Responsibility Principle (SRP) from SOLID design principles.
- Promotes maintainability, testability, and readability.
Example of High Cohesion:
Here’s a UserService
class designed with high cohesion:
public class UserService
{
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
public UserService(IUserRepository userRepository, IEmailService emailService)
{
_userRepository = userRepository;
_emailService = emailService;
}
public User GetUserById(int id)
{
return _userRepository.GetById(id);
}
public void RegisterUser(User user)
{
_userRepository.Add(user);
_emailService.SendWelcomeEmail(user.Email);
}
}
Why It’s High Cohesion:
- The
UserService
class handles user-related functionality only. - Responsibilities like database access and email sending are delegated to other classes, adhering to SRP.
Benefits of High Cohesion:
- Easier Maintenance: Changes are localized to specific classes.
- Better Testability: Focused responsibilities make it easier to write unit tests.
- Improved Reusability: Components can be reused in other parts of the system.
Low Cohesion
Characteristics of Low Cohesion:
- A single module or class takes on multiple unrelated responsibilities.
- Violates the Single Responsibility Principle.
- Leads to tightly coupled and harder-to-maintain code.
Example of Low Cohesion:
Here’s an example of a UserManager
class with low cohesion:
public class UserManager
{
public User GetUserById(int id)
{
// Logic to fetch user from database
Console.WriteLine("Fetching user from database");
return new User { Id = id, Name = "John Doe" };
}
public void SendEmail(string email, string message)
{
// Logic to send an email
Console.WriteLine($"Sending email to {email}: {message}");
}
public void Log(string message)
{
// Logic to log a message
Console.WriteLine($"Log: {message}");
}
}
Why It’s Low Cohesion:
- The
UserManager
class handles database operations, email sending, and logging, which are unrelated responsibilities. - Violates SRP by mixing different concerns.
Drawbacks of Low Cohesion:
- Difficult to Maintain: A change in one responsibility may unintentionally affect others.
- Harder to Test: Writing focused unit tests is challenging when a class does too much.
- Poor Reusability: The class is tightly coupled and cannot be easily reused elsewhere.
Key Differences Between High and Low Cohesion
Aspect | High Cohesion | Low Cohesion |
---|---|---|
Focus | Single, well-defined responsibility | Multiple, unrelated responsibilities |
Maintainability | Easy to maintain and extend | Difficult to maintain and extend |
Testability | Easy to test in isolation | Hard to test due to mixed concerns |
Reusability | High reusability of focused components | Low reusability due to tight coupling |
How to Achieve High Cohesion
- Follow the Single Responsibility Principle (SRP):
- Ensure each class or module focuses on one responsibility.
- Use Abstraction:
- Delegate unrelated responsibilities to different interfaces or classes.
- Refactor Often:
- Regularly review your code to identify and separate mixed responsibilities.
- Leverage Design Patterns:
- Patterns like Factory, Strategy, and Observer can help separate concerns.
Real-World Example: Refactoring to High Cohesion
Before Refactoring (Low Cohesion):
public class OrderManager
{
public void PlaceOrder(Order order)
{
Console.WriteLine("Saving order to database");
Console.WriteLine("Sending order confirmation email");
Console.WriteLine("Logging order details");
}
}
After Refactoring (High Cohesion):
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IEmailService _emailService;
private readonly ILogger _logger;
public OrderService(IOrderRepository orderRepository, IEmailService emailService, ILogger logger)
{
_orderRepository = orderRepository;
_emailService = emailService;
_logger = logger;
}
public void PlaceOrder(Order order)
{
_orderRepository.Save(order);
_emailService.SendOrderConfirmation(order.Email);
_logger.Log("Order placed successfully");
}
}
Conclusion
Cohesion is a fundamental principle of clean code and software design. High cohesion results in maintainable, testable, and reusable code, while low cohesion can lead to tightly coupled, hard-to-manage systems. By following best practices like SRP, abstraction, and refactoring, you can ensure your codebase remains flexible and easy to work with.
Keep these principles in mind as you design and refactor your applications, and you’ll build systems that stand the test of time.