Single Responsibility Principle
About 857 wordsAbout 3 min
2025-03-27
Definition
An object should contain only a single responsibility, and the responsibility is completely encapsulated in a class.
Effect
- The more responsibilities a class has, the less likely it is to be reused.
- When one responsibility changes, it may affect the operation of other responsibilities.
- Separate these responsibilities and encapsulate different responsibilities in different classes.
- Encapsulate different reasons for change in different classes.
- The single responsibility principle is a guideline for achieving high cohesion and low coupling.
Advantages
Organize code.
Let's imagine a car mechanic. He uses many tools to work together. These tools are divided into types: pliers, screwdrivers, hammers, wrenches, etc. How does he organize and manage these tools? He uses many small drawers to store these tools in different categories. These drawers are actually similar to modules and are specifically used to manage various classes.
Reduce fragility.
When a class has multiple reasons to change, it becomes fragile, and changes in one place can lead to unexpected consequences in other places.
More loose coupling.
More responsibilities lead to higher coupling. Coupling is also a responsibility. High coupling leads to high dependence, which means it is difficult to maintain.
Code changes.
It is easier to refactor for single-responsibility modules. If you want to get a shotgun effect, give your class more responsibilities.
Maintainability.
It is easier to maintain a class with a single responsibility than a monolithic class.
Easy to test.
Testing a class with a single goal requires only a few test classes.
Easy to debug.
It is easier to find problems in a single-responsibility class.
How to identify that SRP is violated?
The class has too many dependencies.
The class constructor has too many parameters, which means that the test has too many dependencies and needs to mock too many test input parameters, which usually means that SRP has been violated.
The method has too many parameters.
Similar to the class constructor, method parameters mean dependencies.
The test class becomes complicated.
If the test has too many variables, it means that the class has too many responsibilities.
The class or method is too long.
If the method is too long, it means that there is too much content and too many responsibilities. Generally, a class should not exceed 200-250 lines.
Descriptive names.
If you need to describe your class, method or package, you need to use statements such as "xxx and xxx", which means that SRP may be violated.
Classes with low cohesion.
Cohesion is a very important concept. Although cohesion is related to structural concepts, cohesion is very related to SRP. As in the previous forum case, if a class does not represent a high aggregation, it means low cohesion, which may mean breaking SRP. A characteristic of low cohesion: a class has two fields, one of which is used by some methods, and the other is used by other methods.
Changes in one place affect another place.
If adding new features or simply refactoring in one code place affects other unrelated places, it means that this code may break SRP.
Shotgun Effect.
If a small change causes a whole body, it means that SRP is broken.
Unable to encapsulate modules.
For example, using the Spring framework, you use @Configuration or XML configuration. If you cannot encapsulate a Bean in a configuration, it means it has too many responsibilities. Spring configuration should hide internal beans and expose the least interface. If you need to change the Spring configuration for multiple reasons, it may break SRP.
Example
Counter Example
class Modem {
void dial(String pno); // dial
void hangup(); // hang up
void send(char c); // send data
char recv(); // receive data
};
At first glance, it seems nothing, but in fact the Modem
class is responsible for connection management and data management. These two parts are actually unrelated.
Positive Example
#ifndef DIALER_H
#define DIALER_H
#include <string>
class Dialer {
public:
void dial(const std::string& phoneNumber) {
// Execute dial operation
}
void hangup() {
// Execute hangup operation
}
};
#endif
#ifndef CONNECTIONMANAGER_H
#define CONNECTIONMANAGER_H
#include "Dialer.h"
class ConnectionManager {
private:
Dialer dialer;
public:
void dial(const std::string& phoneNumber) {
dialer.dial(phoneNumber);
}
void hangup() {
dialer.hangup();
}
};
#endif
#ifndef DATASENDER_H
#define DATASENDER_H
class DataSender {
public:
void send(char c) {
// Execute the data sending operation
}
};
#endif
#ifndef DATARECEIVER_H
#define DATARECEIVER_H
class DataReceiver {
public:
char recv() {
// Execute the data receiving operation
return 'a'; // Assume that the character 'a' is received
}
};
#endif
#ifndef DATATRANSFERMANAGER_H
#define DATATRANSFERMANAGER_H
#include "DataSender.h"
#include "DataReceiver.h"
class DataTransferManager {
private:
DataSender sender;
DataReceiver receiver;
public:
void send(char c) {
sender.send(c);
}
char recv() {
return receiver.recv();
}
};
#endif
Summary
The core of this principle is to master the granularity of code organization. Minimize this granularity as much as possible to increase code reusability and the scope of impact of code changes.
Contributors
Changelog
270fc
-feat(docs): add solid principles serieson
Copyright
Copyright Ownership:dingyuqi
License under:Attribution-NonCommercial-NoDerivatives 4.0 International (CC-BY-NC-ND-4.0)