Zgodnie z dokumentacją Microsoft, artykułem Wikipedia SOLID lub większością architektów IT musimy upewnić się, że każda klasa ma tylko jedną odpowiedzialność. Chciałbym wiedzieć, dlaczego, ponieważ jeśli wszyscy wydają się zgadzać z tą zasadą, nikt nie wydaje się zgadzać co do przyczyn tej reguły.
Niektórzy wskazują na lepszą konserwację, inni twierdzą, że zapewnia łatwe testowanie lub czyni klasę bardziej niezawodną lub bezpieczną. Co jest poprawne i co to właściwie znaczy? Dlaczego usprawnia konserwację, testowanie lub kod jest bardziej niezawodny?
design-patterns
architecture
solid
single-responsibility
Bastien Vandamme
źródło
źródło
Odpowiedzi:
Modułowość. Każdy przyzwoity język da ci możliwość sklejenia ze sobą kawałków kodu, ale nie ma ogólnego sposobu na odblokowanie dużego kawałka kodu bez przeprowadzenia przez programistę operacji na źródle. Łącząc wiele zadań w jedną konstrukcję kodu, pozbawiasz siebie i innych możliwości łączenia ich elementów na inne sposoby i wprowadzasz niepotrzebne zależności, które mogą spowodować, że zmiany jednego elementu wpłyną na inne.
SRP ma tak samo zastosowanie do funkcji, jak i do klas, ale główne języki OOP są stosunkowo słabe w łączeniu funkcji razem.
źródło
Lepsza konserwacja, łatwe testowanie, szybsze usuwanie błędów to tylko (bardzo przyjemne) wyniki zastosowania SRP. Głównym powodem (jak to ujmuje Robert C. Matin) jest:
Innymi słowy, SRP podnosi lokalizację zmiany .
SRP promuje również kod DRY. Tak długo, jak mamy zajęcia, na których spoczywa tylko jedna odpowiedzialność, możemy z nich korzystać w dowolnym miejscu. Jeśli mamy klasę, która ma dwie obowiązki, ale potrzebujemy tylko jednej z nich, a druga przeszkadza, mamy 2 opcje:
źródło
Łatwo jest utworzyć kod, aby rozwiązać konkretny problem. Bardziej skomplikowane jest tworzenie kodu, który rozwiązuje ten problem, jednocześnie umożliwiając bezpieczne wprowadzanie późniejszych zmian. SOLID zapewnia zestaw praktyk, które poprawiają kod.
Co do poprawności: Wszystkie trzy. Są to wszystkie zalety korzystania z pojedynczej odpowiedzialności i powód, dla którego powinieneś z niej korzystać.
Co oznaczają:
Spróbuj utworzyć kod przez chwilę, zachowując tę zasadę, i wróć do niego później, aby wprowadzić pewne zmiany. Zobaczysz ogromną przewagę, jaką zapewnia.
Robisz to samo dla każdej klasy i otrzymujesz więcej klas, wszystkie zgodne z SRP. Sprawia, że struktura jest bardziej złożona z punktu widzenia połączeń, ale uzasadnia to prostota każdej klasy.
źródło
Oto argumenty, które moim zdaniem potwierdzają twierdzenie, że zasada pojedynczej odpowiedzialności jest dobrą praktyką. Podaję również linki do dalszej literatury, w których można przeczytać jeszcze bardziej szczegółowe uzasadnienia - i bardziej wymowne niż moje:
Lepsza konserwacja : idealnie, za każdym razem, gdy funkcjonalność systemu musi się zmienić, będzie jedna i tylko jedna klasa, która musi zostać zmieniona. Jasne przyporządkowanie klas i obowiązków oznacza, że każdy programista zaangażowany w projekt może zidentyfikować, która to klasa. (Jak zauważył @ MaciejChałapuk, patrz Robert C. Martin, „Clean Code” .)
Łatwiejsze testowanie : idealnie, klasy powinny mieć jak najmniejszy publiczny interfejs, a testy powinny dotyczyć tylko tego publicznego interfejsu. Jeśli nie możesz przeprowadzić jasnego testu, ponieważ wiele części twojej klasy jest prywatnych, oznacza to wyraźny znak, że twoja klasa ma zbyt wiele obowiązków i że powinieneś podzielić ją na mniejsze podklasy. Zauważ, że dotyczy to również języków, w których nie ma członków klasy „publicznej” lub „prywatnej”; posiadanie małego publicznego interfejsu oznacza, że kod klienta jest bardzo jasny, z których części klasy powinien korzystać. (Aby uzyskać więcej informacji, zobacz Kent Beck, „Test-Driven Development” ).
Solidny kod : Twój kod nie zawiedzie się mniej lub bardziej często, ponieważ jest dobrze napisany; ale, jak cały kod, jego ostatecznym celem nie jest komunikowanie się z maszyną , ale z innymi programistami (patrz Kent Beck, „Wzorce implementacji” , rozdział 1.) Łatwiej jest zrozumieć przejrzystą bazę kodu, więc mniej błędów zostanie wprowadzonych , a mniej czasu upłynie między wykryciem błędu a naprawieniem go.
źródło
Jest wiele powodów, ale podoba mi się podejście stosowane we wczesnych programach UNIX: Zrób jedną rzecz dobrze. Jest to wystarczająco trudne, aby zrobić to z jedną rzeczą, a im trudniej jest robić, tym więcej próbujesz robić.
Innym powodem jest ograniczenie i kontrolowanie skutków ubocznych. Uwielbiałem mój otwieracz do drzwi z ekspresem do kawy. Niestety kawa zwykle przelewała się, gdy miałem gości. Zapomniałem zamknąć drzwi po tym, jak zrobiłem kawę pewnego dnia i ktoś ją ukradł.
Z psychologicznego punktu widzenia możesz śledzić tylko kilka rzeczy na raz. Ogólne szacunki to siedem plus minus dwa. Jeśli klasa robi wiele rzeczy, musisz śledzić je wszystkie naraz. Zmniejsza to twoją zdolność do śledzenia tego, co robisz. Jeśli klasa robi trzy rzeczy i chcesz tylko jedną z nich, możesz wyczerpać swoją zdolność do śledzenia rzeczy, zanim zrobisz cokolwiek z klasą.
Robienie wielu rzeczy zwiększa złożoność kodu. Oprócz najprostszego kodu rosnąca złożoność zwiększa prawdopodobieństwo błędów. Z tego punktu widzenia chcesz, aby zajęcia były jak najprostsze.
Testowanie klasy, która wykonuje jedną rzecz, jest znacznie prostsze. Nie musisz sprawdzać, czy druga rzecz, którą klasa zrobiła lub nie zdarzyła się przy każdym teście. Nie musisz również naprawiać uszkodzonych warunków i ponownie testować, gdy jeden z tych testów zakończy się niepowodzeniem.
źródło
Ponieważ oprogramowanie jest organiczne. Wymagania stale się zmieniają, więc musisz manipulować komponentami przy jak najmniejszym bólu głowy. Nieprzestrzeganie zasad SOLID może spowodować, że baza kodu będzie konkretna.
Wyobraź sobie dom z betonową ścianą nośną. Co się stanie, gdy wyjmiesz tę ścianę bez żadnego wsparcia? Dom prawdopodobnie się zawali. W oprogramowaniu nie chcemy tego, więc konstruujemy aplikacje w taki sposób, abyś mógł łatwo przenosić / wymieniać / modyfikować komponenty bez powodowania dużych szkód.
źródło
Śledzę tę myśl:
1 Class = 1 Job
.Korzystanie z analogii fizjologii: silnik (układ nerwowy), oddychanie (płuca), pokarmowy (żołądek), węchowy (obserwacja) itp. Każdy z nich będzie miał podzbiór kontrolerów, ale każdy z nich ma tylko 1 odpowiedzialność, niezależnie od tego, czy ma zarządzać sposób, w jaki działa każdy z ich podsystemów lub czy są one podsystemem punktu końcowego i wykonują tylko jedno zadanie, takie jak podniesienie palca lub wyhodowanie mieszków włosowych.
Nie należy mylić faktu, że może on działać jako kierownik zamiast pracownika. Niektórzy pracownicy ostatecznie awansują na kierownika, gdy praca, którą wykonują, stała się zbyt skomplikowana, aby jeden proces mógł sam sobie poradzić.
Najbardziej skomplikowaną częścią tego, czego doświadczyłem, jest wiedza, kiedy wyznaczyć klasę jako operatora, przełożonego lub kierownika. Niezależnie od tego musisz obserwować i oznaczać jego funkcjonalność dla 1 odpowiedzialności (Operator, Kierownik lub Kierownik).
Gdy klasa / obiekt wykonuje więcej niż jedną z tych ról typu, przekonasz się, że w całym procesie zaczną występować problemy z wydajnością lub będą występować wąskie gardła.
źródło
Lung
klasa, uważam, że instancjaPerson
powinna być nadalBreathe
, a zatemPerson
klasa powinna zawierać wystarczającą logikę, aby przynajmniej przekazać obowiązki związane z tą metodą. Sugerowałbym ponadto, że las połączonych ze sobą podmiotów, które są dostępne tylko za pośrednictwem wspólnego właściciela, jest często łatwiejszy do uzasadnienia niż las, który ma wiele niezależnych punktów dostępu.Person
klasa ma za zadanie delegowanie całej funkcjonalności związanej z monstrualnie nadmiernie skomplikowanym bytem „prawdziwym człowiekiem”, w tym skojarzeniem jego różnych części . Posiadanie jednostki, która robi 500 rzeczy, jest brzydkie, ale jeśli tak właśnie działa modelowany system rzeczywisty, posiadanie klasy, która deleguje 500 funkcji przy zachowaniu jednej tożsamości, może być lepsze niż robienie wszystkiego z rozłącznymi kawałkami.Person
ale nadal raportować w górę łańcucha o sukcesie / porażce, ale niekoniecznie wpływając na drugi. 500 funkcji (choć podane jako przykład, byłyby za burtą i nie byłyby obsługiwane) staram się zachować tego typu funkcjonalność pochodną i modułową.Fred.vision.lookAt(George)
sens, ale pozwolenie, by kod powiedział, żesomeEyes = Fred.vision; someTubes = Fred.digestion
wydaje się trudne, ponieważ przesłania związek międzysomeEyes
asomeTubes
.Zwłaszcza w przypadku tak ważnej zasady, jak pojedyncza odpowiedzialność, osobiście oczekiwałbym, że istnieje wiele powodów, dla których ludzie przyjmują tę zasadę.
Niektóre z tych przyczyn mogą być:
Należy również pamiętać, że SRP zawiera pakiet zasad SOLID. Przestrzeganie SRP i ignorowanie reszty jest tak samo złe, jak nie wykonywanie SRP. Dlatego nie powinieneś oceniać SRP sam w sobie, ale w kontekście wszystkich zasad SOLID.
źródło
... test is not affected by other responsibilities the class has
czy mógłbyś rozwinąć tę kwestię?Najlepszym sposobem na zrozumienie znaczenia tych zasad jest potrzeba.
Kiedy byłem początkującym programistą, nie myślałem o projektowaniu, w rzeczywistości nawet nie wiedziałem, że istnieją wzorce projektowe. W miarę rozwoju moich programów zmiana jednej rzeczy oznaczała zmianę wielu innych rzeczy. Trudno było wyśledzić błędy, kod był ogromny i powtarzalny. Hierarchia obiektów była niewielka, wszystko było wszędzie. Dodanie czegoś nowego lub usunięcie czegoś starego spowodowałoby błędy w innych częściach programu. Domyśl.
W małych projektach może to nie mieć znaczenia, ale w dużych projektach może być bardzo koszmarnie. Później, kiedy natknąłem się na koncepcje wzorów projektowych, powiedziałem sobie: „o tak, zrobienie tego znacznie ułatwiłoby to wtedy”.
Naprawdę nie możesz zrozumieć znaczenia wzorców projektowych, dopóki nie pojawi się taka potrzeba. Szanuję wzorce, ponieważ z doświadczenia mogę powiedzieć, że sprawiają, że utrzymanie kodu jest łatwe i niezawodne.
Jednak, podobnie jak ty, wciąż nie jestem pewien co do „łatwych testów”, ponieważ nie miałem jeszcze potrzeby wchodzenia w testy jednostkowe.
źródło
Odpowiedź jest, jak zauważyli inni, wszystkie z nich są poprawne i wszystkie wzajemnie się łączą, łatwiej przetestować ułatwia konserwację sprawia, że kod jest bardziej niezawodny, ułatwia konserwację i tak dalej ...
Wszystko sprowadza się do kluczowej zasady - kod powinien być tak mały i robić tak mało, jak to konieczne, aby wykonać zadanie. Dotyczy to aplikacji, pliku lub klasy tak samo jak funkcji. Im więcej rzeczy robi dany fragment kodu, tym trudniej jest go zrozumieć, utrzymać, rozszerzyć lub przetestować.
Myślę, że można to podsumować jednym słowem: zakres. Zwróć szczególną uwagę na zakres artefaktów, im mniej elementów w danym punkcie aplikacji, tym lepiej.
Rozszerzony zakres = większa złożoność = więcej sposobów, aby coś poszło nie tak.
źródło
Jeśli klasa jest zbyt duża, trudno jest ją utrzymać, przetestować i zrozumieć, inne odpowiedzi obejmowały tę wolę.
Możliwe jest, że klasa będzie miała więcej niż jedną odpowiedzialność bez problemów, ale wkrótce natrafisz na problemy ze zbyt złożonymi klasami.
Jednak posiadanie prostej zasady „tylko jedna odpowiedzialność” sprawia, że łatwiej jest wiedzieć, kiedy potrzebujesz nowej klasy .
Jakkolwiek zdefiniowanie „odpowiedzialności” jest trudne, nie oznacza to „robienia wszystkiego, co mówi specyfikacja aplikacji”, prawdziwą umiejętnością jest umieć rozłożyć problem na małe jednostki „odpowiedzialności”.
źródło