Imagine you have a toy car with buttons to start the engine and show the dashboard. The Single Responsibility Principle (SRP) means that each button on the toy car has only one job or responsibility.
For example, the start button's job is to make the engine start. It doesn't do anything else, like showing the dashboard. And the dashboard button's job is to show the dashboard, not to start the engine.
This makes the toy car easier to understand and use. It also helps if we want to change or fix something. We can just focus on one button without worrying about other things.
In real programming, we apply SRP by making sure each part of our code, like classes or functions, has only one job or responsibility. This makes the code easier to read, test, and maintain. It's like having each part of our code do its own special thing, just like each button on the toy car.
In this example, the Car class has two responsibilities: starting the car's engine and displaying the car's dashboard.
// MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
private Car _car;
public MainWindow()
{
InitializeComponent();
_car = new Car();
}
private void StartCarButton_Click(object sender, RoutedEventArgs e)
{
_car.StartCar();
}
private void ShowDashboardButton_Click(object sender, RoutedEventArgs e)
{
_car.ShowDashboard();
}
}
// Car.cs
public class Car
{
public Car()
{
}
public void StartCar()
{
// Code to start the car's engine.
Console.WriteLine("Engine started!");
}
public void ShowDashboard()
{
// Code to display the car's dashboard.
Console.WriteLine("Dashboard displayed!");
}
}
To follow the Single Responsibility Principle (SRP), we can separate these responsibilities into different classes.
// Car.cs
public class Car
{
private Engine _engine;
private DashboardViewModel _dashboardViewModel;
public Car()
{
_engine = new Engine();
_dashboardViewModel = new DashboardViewModel();
}
public void StartCar()
{
_engine.StartEngine();
}
public void ShowDashboard()
{
_dashboardViewModel.Show();
}
}
// Engine.cs
public class Engine
{
public void StartEngine()
{
// Code to start the car's engine.
Console.WriteLine("Engine started!");
}
}
// DashboardViewModel.cs
public class DashboardViewModel
{
public void Show()
{
// Code to display the car's dashboard.
Console.WriteLine("Dashboard displayed!");
}
}
The Engine class is responsible for starting the car's engine, and the DashboardViewModel class is responsible for displaying the car's dashboard. The Car class now delegates these responsibilities to the appropriate classes.
By separating the responsibilities, each class focuses on a single job. The Car class becomes more focused on coordinating these different responsibilities rather than implementing them directly. This promotes better code organization, maintainability, and makes it easier to extend and modify the code in the future.
It's easy to make mistakes with SRP, so watch out for these signs that you might be having some problems:
Bloated Classes: Creating classes that have too many responsibilities. This leads to classes that are difficult to understand, maintain, and test. It's important to keep classes focused on a single responsibility.
Violating the "One Reason to Change" Principle: When a class has multiple responsibilities, it becomes more likely that a change in one area will affect other unrelated areas of the code. This violates the principle of having only one reason to change a class.
Mixing Presentation and Business Logic: Combining user interface (UI) code with business logic in the same class. It's better to separate these concerns and have dedicated classes for handling UI interactions and separate classes for business logic.
God Classes: Creating classes that try to do everything and have a wide range of responsibilities. God classes become difficult to understand and maintain. It's important to break down functionality into smaller, focused classes.
Lack of Cohesion: When a class has unrelated or loosely related responsibilities, it lacks cohesion. It's important to ensure that each class has a clear and focused purpose, with methods and properties that work together towards that purpose.
Overly Granular Classes: On the other hand, having classes that are too small and granular can also be a mistake. It can lead to excessive class fragmentation, making it harder to manage and navigate the codebase.