Nur weil ein System komplex ist, heißt das noch lange nicht, dass Sie es komplizieren müssen . Wenn Sie eine Klasse haben, die zu viele Abhängigkeiten (oder Mitbearbeiter) hat:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
... dann wurde es viel zu kompliziert und Sie folgen SRP nicht wirklich , oder? Ich wette, wenn Sie aufschreiben, was MyAwesomeClass
auf einer CRC-Karte funktioniert , passt es nicht auf eine Karteikarte, oder Sie müssen in wirklich winzigen unleserlichen Buchstaben schreiben.
Was Sie hier gesehen haben, ist, dass Ihre Jungs stattdessen nur das Prinzip der Schnittstellentrennung befolgt haben und es vielleicht auf ein Extrem gebracht haben, aber das ist eine ganz andere Geschichte. Sie könnten argumentieren, dass es sich bei den Abhängigkeiten um Domänenobjekte handelt (was jedoch vorkommt). Eine Klasse, die 20 Domänenobjekte gleichzeitig verarbeitet, ist etwas zu umfangreich.
TDD liefert Ihnen einen guten Indikator dafür, wie viel eine Klasse leistet. Unverblümt; Wenn eine Testmethode Setup-Code enthält, dessen Schreiben ewig dauert (auch wenn Sie die Tests überarbeiten), müssen Sie MyAwesomeClass
wahrscheinlich zu viel tun.
Wie lösen Sie dieses Rätsel? Sie verlagern die Zuständigkeiten auf andere Klassen. Es gibt einige Schritte, die Sie für eine Klasse ausführen können, bei der dieses Problem auftritt:
- Identifizieren Sie alle Aktionen (oder Verantwortlichkeiten), die Ihre Klasse mit ihren Abhängigkeiten ausführt.
- Gruppieren Sie die Aktionen nach eng verwandten Abhängigkeiten.
- Redelegate! Das heißt, jede der identifizierten Aktionen wird entweder neuen oder (was noch wichtiger ist) anderen Klassen zugeordnet.
Ein abstraktes Beispiel für die Umgestaltung von Verantwortlichkeiten
Lassen Sie C
eine Klasse sein , die mehrere Abhängigkeiten hat D1
, D2
, D3
, D4
dass Sie Refactoring müssen weniger verwenden. Wenn wir herausfinden, welche Methoden C
die Abhängigkeiten aufrufen, können wir eine einfache Liste davon erstellen:
D1
- performA(D2)
,performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Wenn wir uns die Liste ansehen, können wir das sehen D1
und D2
sind miteinander verwandt, da die Klasse sie irgendwie zusammen braucht. Wir können auch sehen, dass D4
Bedürfnisse D3
. Wir haben also zwei Gruppierungen:
Group 1
- D1
<->D2
Group 2
- D4
->D3
Die Gruppierungen sind ein Indikator dafür, dass die Klasse jetzt zwei Verantwortlichkeiten hat.
Group 1
- Eine zur Behandlung der aufrufenden zwei Objekte, die sich gegenseitig benötigen. Vielleicht können Sie Ihre Klasse C
die Notwendigkeit beseitigen lassen, beide Abhängigkeiten zu behandeln, und eine von ihnen kann stattdessen diese Aufrufe behandeln. In dieser Gruppierung ist es offensichtlich, D1
dass ein Verweis auf haben könnte D2
.
Group 2
- Die andere Verantwortung benötigt ein Objekt, um ein anderes aufzurufen. Kann nicht D4
behandeln D3
statt Klasse? Dann können wir wahrscheinlich D3
aus der Klasse C
streichen, indem D4
wir stattdessen die Anrufe erledigen lassen .
Nehmen Sie meine Antwort nicht in Stein gemeißelt, da das Beispiel sehr abstrakt ist und viele Annahmen macht. Ich bin mir ziemlich sicher, dass es weitere Möglichkeiten gibt, dies umzugestalten, aber zumindest könnten die Schritte Ihnen dabei helfen, eine Art Prozess zu entwickeln, um Verantwortlichkeiten zu verschieben, anstatt die Klassen aufzuteilen.
Bearbeiten:
Zu den Kommentaren sagt @Emmad Karem :
"Wenn Ihre Klasse 20 Parameter im Konstruktor hat, hört es sich nicht so an, als ob Ihr Team genau weiß, was SRP ist. Wenn Sie eine Klasse haben, die nur eines tut, wie hat sie 20 Abhängigkeiten?" Wenn Sie eine Customer-Klasse haben, ist es nicht ungewöhnlich, 20 Parameter im Konstruktor zu haben.
Es ist richtig, dass DAO-Objekte in der Regel viele Parameter haben, die Sie in Ihrem Konstruktor festlegen müssen, und die Parameter sind normalerweise einfache Typen wie Zeichenfolgen. Im Beispiel einer Customer
Klasse können Sie ihre Eigenschaften jedoch auch in anderen Klassen gruppieren, um die Arbeit zu vereinfachen. B. eine Address
Klasse mit Straßen und eine Zipcode
Klasse, die die Postleitzahl enthält und die Geschäftslogik wie die Datenüberprüfung übernimmt:
public class Address {
private String street1;
//...
private Zipcode zipcode;
// easy to extend
public bool isValid() {
return zipcode.isValid();
}
}
public class Zipcode {
private string zipcode;
public bool isValid() {
// return regex match that zipcode contains numbers
}
}
Diese Sache wird im Blog-Beitrag "Niemals, niemals, niemals, niemals in Java (oder zumindest oft) String verwenden" weiter besprochen . Alternativ zur Verwendung von Konstruktoren oder statischen Methoden, um die Erstellung der Unterobjekte zu vereinfachen, können Sie ein Fluid Builder-Muster verwenden .