To, że system jest złożony, nie oznacza, że musisz go skomplikować . Jeśli masz klasę, która ma zbyt wiele zależności (lub współpracowników), takich jak to:
public class MyAwesomeClass {
public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
// Assign it all
}
}
... to stało się zbyt skomplikowane i tak naprawdę nie śledzisz SRP , prawda? Założę się, że jeśli zapisałeś, co MyAwesomeClass
robi na karcie CRC , nie zmieściłoby się na karcie indeksu lub będziesz musiał pisać naprawdę małymi, nieczytelnymi literami.
To, co tu masz, to fakt, że twoi faceci przestrzegali tylko zasady segregacji interfejsów i mogli doprowadzić do skrajności, ale to zupełnie inna historia. Można argumentować, że zależności są obiektami domeny (co się zdarza), jednak posiadanie klasy, która obsługuje 20 obiektów domeny w tym samym czasie, rozciąga je nieco za daleko.
TDD zapewni ci dobry wskaźnik tego, ile klasa robi. Mówiąc wprost; jeśli metoda testowa ma kod instalacyjny, którego pisanie trwa wiecznie (nawet jeśli refaktoryzujesz testy), to MyAwesomeClass
prawdopodobnie masz zbyt wiele do zrobienia.
Jak więc rozwiązać tę zagadkę? Przenosisz obowiązki na inne klasy. Istnieje kilka kroków, które możesz podjąć w przypadku klasy, która ma ten problem:
- Zidentyfikuj wszystkie działania (lub obowiązki), które klasa wykonuje przy swoich zależnościach.
- Pogrupuj działania według ściśle powiązanych zależności.
- Redelegate! Oznacza to, że każdą ze zidentyfikowanych akcji przekieruj na nowe lub (co ważniejsze) inne klasy.
Abstrakcyjny przykład obowiązków związanych z refaktoryzacją
Niech C
będzie, że klasa ma kilka zależności D1
, D2
, D3
, D4
że trzeba byłaby używać mniej. Kiedy ustalimy, jakie metody C
wywołują zależności, możemy utworzyć prostą listę:
D1
- performA(D2)
,performB()
D2
- performD(D1)
D3
- performE()
D4
- performF(D3)
Patrząc na listę, widzimy to D1
i D2
są ze sobą spokrewnieni, ponieważ klasa potrzebuje ich jakoś razem. Możemy również zobaczyć te D4
potrzeby D3
. Mamy więc dwie grupy:
Group 1
- D1
<->D2
Group 2
- D4
->D3
Grupy są wskaźnikiem, że klasa ma teraz dwie obowiązki.
Group 1
- Jeden do obsługi wywoływania dwóch obiektów, które potrzebują siebie nawzajem. Być może możesz pozwolić swojej klasie C
wyeliminować potrzebę obsługi obu zależności i pozostawić jedną z nich obsługującą te połączenia. W tej grupie oczywiste jest, że D1
może mieć odniesienie D2
.
Group 2
- Druga odpowiedzialność potrzebuje jednego obiektu do wywołania drugiego. Nie możesz sobie D4
poradzić D3
zamiast swojej klasy? Wtedy prawdopodobnie możemy wyeliminować D3
z klasy C
, pozwalając D4
zamiast tego wykonywać połączenia.
Nie bierz mojej odpowiedzi tak mocno osadzonej w kamieniu, ponieważ przykład jest bardzo abstrakcyjny i zawiera wiele założeń. Jestem prawie pewien, że istnieje więcej sposobów, aby to zmienić, ale przynajmniej te kroki mogą pomóc ci uzyskać jakiś proces przenoszenia obowiązków zamiast podziału klas.
Edytować:
Wśród komentarzy @Emmad Karem mówi:
„Jeśli twoja klasa ma 20 parametrów w konstruktorze, nie brzmi to tak, jakby Twój zespół wiedział, czym jest SRP. Jeśli masz klasę, która robi tylko jedną rzecz, to jak ma 20 zależności?” - myślę, że jeśli mieć klasę Customer, nie jest niczym dziwnym mieć 20 parametrów w konstruktorze.
Prawdą jest, że obiekty DAO mają zwykle wiele parametrów, które należy ustawić w konstruktorze, a parametry są zwykle prostymi typami, takimi jak łańcuch znaków. Jednak w przykładzie Customer
klasy nadal można grupować jej właściwości w innych klasach, aby uprościć sprawę. Na przykład, mając Address
klasę z ulicami i Zipcode
klasę, która zawiera kod pocztowy i obsługuje logikę biznesową, taką jak sprawdzanie poprawności danych:
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
}
}
Zagadnienie to zostało omówione w poście na blogu „Nigdy, nigdy, nigdy nie używaj Stringa w Javie (lub przynajmniej często)” . Alternatywą dla użycia konstruktorów lub metod statycznych w celu ułatwienia tworzenia obiektów podrzędnych jest użycie wzorca konstruktora płynów .