Overview
Incorporating the principles of cohesion and coupling in object-oriented design is essential for creating maintainable and scalable systems. Cohesion refers to how closely related and focused the responsibilities of a single module are, aiming for high cohesion within modules. Coupling, on the other hand, deals with how dependent modules are on each other, with the goal being to minimize dependencies (loose coupling). Understanding and applying these principles effectively can lead to more robust, flexible, and modular software systems.
Key Concepts
- High Cohesion: Ensures that classes or modules have a well-defined purpose, leading to easier maintenance and scalability.
- Loose Coupling: Focuses on reducing dependencies between modules, which enhances the reusability and adaptability of the code.
- Design Patterns: Many design patterns, such as MVC (Model-View-Controller) and Observer, inherently promote high cohesion and loose coupling.
Common Interview Questions
Basic Level
- What do cohesion and coupling mean in the context of object-oriented design?
- Can you give an example of high cohesion and loose coupling in class design?
Intermediate Level
- How do design patterns support high cohesion and loose coupling?
Advanced Level
- How would you refactor a tightly coupled system to improve its design?
Detailed Answers
1. What do cohesion and coupling mean in the context of object-oriented design?
Answer: In object-oriented design, cohesion refers to the degree to which the elements inside a module belong together. High cohesion within a module means it is focused on a single task or closely related tasks, making it more understandable and easier to maintain. Coupling, on the other hand, describes how dependent modules are on each other. Loose coupling is desirable as it means changes in one module are less likely to require changes in another, leading to more modular and flexible code.
Key Points:
- High cohesion within modules simplifies maintenance and enhancement.
- Loose coupling between modules facilitates modularity and flexibility.
- Balancing these principles is key to effective object-oriented design.
Example:
public class User
{
// High cohesion - User class focuses solely on user properties and behavior
public int Id { get; set; }
public string Name { get; set; }
public void Login()
{
Console.WriteLine("User logged in");
}
}
public class UserService
{
// Loose coupling - UserService depends on User class but not on specific details
public void CreateUser(User user)
{
Console.WriteLine("Creating user");
// Implementation to create a user
}
}
2. Can you give an example of high cohesion and loose coupling in class design?
Answer: Yes, a practical example involves separating concerns in a web application. High cohesion is achieved by creating dedicated classes for specific functionalities, such as a UserController
for handling user input and a UserRepository
for database interactions. Loose coupling is exemplified by using interfaces for dependencies, allowing for flexibility in changing the database layer without affecting the controller logic.
Key Points:
- Separate logic into focused classes (high cohesion).
- Use interfaces to interact between classes (loose coupling).
- Facilitates easy updates and maintenance.
Example:
public interface IUserRepository
{
void AddUser(User user);
}
public class UserRepository : IUserRepository
{
public void AddUser(User user)
{
// Add user to database
Console.WriteLine("User added to database");
}
}
public class UserController
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void RegisterUser(User user)
{
_userRepository.AddUser(user);
Console.WriteLine("User registered");
}
}
3. How do design patterns support high cohesion and loose coupling?
Answer: Design patterns are best practices that solve common design problems in software development. Patterns like MVC (Model-View-Controller) separate concerns into different components, promoting high cohesion. For example, the Model handles data, the View deals with the user interface, and the Controller mediates input, processing it and updating the view or model as needed. The Observer pattern, on the other hand, allows subjects and observers to communicate without being tightly coupled, as observers can subscribe to and unsubscribe from subject notifications without the subject needing to know the details of the observers.
Key Points:
- MVC separates concerns into different components, enhancing cohesion.
- Observer pattern allows for loose coupling between observers and subjects.
- Design patterns provide a framework for solving common design issues.
Example:
// Example demonstrating the Observer pattern promoting loose coupling
public interface IObserver
{
void Update(string message);
}
public class UserNotificationService : IObserver
{
public void Update(string message)
{
Console.WriteLine($"Notification received: {message}");
}
}
public class UserActivity
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void Subscribe(IObserver observer)
{
_observers.Add(observer);
}
public void Unsubscribe(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers(string message)
{
foreach(var observer in _observers)
{
observer.Update(message);
}
}
}
4. How would you refactor a tightly coupled system to improve its design?
Answer: Refactoring a tightly coupled system involves identifying areas where dependencies are too direct and rigid, then introducing abstractions such as interfaces or abstract classes to decouple these dependencies. Dependency Injection (DI) is a powerful technique for achieving this. By passing dependencies into an object rather than hardcoding them within the object, we can decouple components and make the system more modular, easier to test, and flexible to changes.
Key Points:
- Introduce interfaces or abstract classes to abstract away dependencies.
- Use Dependency Injection to reduce tight coupling.
- Refactor incrementally to ensure system integrity is maintained.
Example:
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class OrderProcessor
{
private readonly ILogger _logger;
public OrderProcessor(ILogger logger)
{
_logger = logger;
}
public void ProcessOrder(Order order)
{
// Process order
_logger.Log("Order processed");
}
}
In this example, OrderProcessor
is decoupled from the specific logging mechanism by depending on the ILogger
interface rather than a concrete logger class, adhering to the Dependency Inversion Principle and promoting loose coupling.