„Eine Klasse sollte nur einen Grund haben, geändert zu werden”.
In anderen Worten:
-
Das Prinzip der „Single Responsibility” legt Wert auf Einfachheit, also „ein Objekt, eine Aufgabe”.
-
Es geht darum, die Funktionalität von Objekten und deren Beziehungskomplexität zu reduzieren.
-
Man hat Sorge dafür zu tragen, dass jedes Objekt eine Verantwortung hat, auch wenn es nicht immer einfach ist, ein komplexes Objekt in kleinere und einfachere Komponenten zu zerlegen.
-
Es geht folglich nicht darum, nur eine Methode in einer Klasse zu haben – es geht darum, eine Klasse für eine Sache verantwortlich zu machen.
01: class Journal
02: {
03: private:
04: std::string m_title;
05: std::vector<std::string> m_entries;
06: std::size_t m_count;
07:
08: public:
09: Journal(const std::string& title)
10: : m_title{ title }, m_count{}
11: {}
12:
13: void addEntry(const std::string& entry) {
14: m_count++;
15: std::string text { std::to_string(m_count) + ": " + entry };
16: m_entries.push_back(text);
17: }
18:
19: const auto& getEntries() const { return m_entries; }
20:
21: void save(std::ostream& os) {
22:
23: for (const auto& entry : m_entries) {
24: os << entry << std::endl;
25: }
26: }
27: };- Das obige C++-Beispiel könnte in Ordnung zu sein, solange es nur eine einzige Domänenklasse gibt, hier die Klasse
Journal. Dies ist jedoch in einer realen Anwendung normalerweise eher selten der Fall. - Wenn wir beginnen, weitere Domänenklassen wie
Book,Fileusw. hinzuzufügen, ist die Speichermethodesavefür alle Domänenklassen separat zu implementieren. - Das eigentliche Problem entsteht, wenn die Funktionalität für die Datenablage geändert werden soll. Zum Beispiel wäre es denkbar, dass die Daten an Stelle in Dateien in einer Datenbank abgelegt werden sollen. In diesem Fall müsste man jede Domänenklassenimplementierung durchlaufen und den gesamten Code ändern. Eine derartige Vorgehensweise ist nicht empehlenswert!
- Der Verstoß gegen das Single-Responsibility-Prinzip in diesem Beispiel ist offensichtlich:
Die Klasse
Journalbesitzt zwei Gründe, um geändert zu werden:- Änderungen im Zusammenhang mit der
Journal-Klasse selbst. - Änderungen im Zusammenhang mit der Persistenz (Datenablage) der
Journal-Klasse.
- Änderungen im Zusammenhang mit der
Wir betrachten eine Überarbeitung des letzten Beispiels. Man könnte sie auch unter der Begrifflichkeit „Separation of Concerns” einordnen:
01: class Journal
02: {
03: private:
04: std::string m_title;
05: std::vector<std::string> m_entries;
06: std::size_t m_count;
07:
08: public:
09: Journal(const std::string& title)
10: : m_title{ title }, m_count{}
11: {}
12:
13: void addEntry(const std::string& entry) {
14: m_count++;
15: std::string text { std::to_string(m_count) + ": " + entry };
16: m_entries.push_back(text);
17: }
18:
19: const auto& getTitle() const { return m_title; }
20: const auto& getEntries() const { return m_entries; }
21: };
22:
23: struct SavingManager
24: {
25: static void save(const Journal& journal, std::ostream& os) {
26:
27: os << journal.getTitle() << std::endl;
28: for (const auto& entry : journal.getEntries()) {
29: os << entry << std::endl;
30: }
31: }
32: };- Die Klasse
Journalkümmert sich jetzt nur um ihre Daten und Funktionen, die mit dem Journal zusammenhängen. - Der Aspekt des Sicherns von Journaldaten ist an einer anderen, zentralen Stelle zusammengefasst:
Klasse
SavingManager. - Wenn Änderungen an der Klasse
SavingManagernotwendig sind, ist ihr gesamter Code an einer Stelle vorhanden.
- Expressiveness – Ausdruckskraft
- Maintainability – Wartbarkeit
- Reusability – Wiederverwendbarkeit
Die Anregungen zu diesem Beispiel finden Sie in
Single Responsibility Principle in C++
von Vishal Chovatija.