Wydaje się całkiem jasne, że „zasada pojedynczej odpowiedzialności” nie oznacza „tylko jednej rzeczy”. Po to są metody.
public Interface CustomerCRUD
{
public void Create(Customer customer);
public Customer Read(int CustomerID);
public void Update(Customer customer);
public void Delete(int CustomerID);
}
Bob Martin mówi, że „klasy powinny mieć tylko jeden powód do zmiany”. Ale trudno jest się ogarnąć, jeśli jesteś programistą w SOLID.
Napisałem odpowiedź na inne pytanie , w którym zasugerowałem, że obowiązki są jak stanowiska pracy, i tańczyłem wokół tego tematu, używając metafory restauracji, aby zilustrować mój punkt widzenia. Ale to wciąż nie określa zestawu zasad, które ktoś mógłby wykorzystać do zdefiniowania obowiązków swoich klas.
Jak to robisz? Jak określasz obowiązki każdej klasy i jak określasz odpowiedzialność w kontekście SRP?
architecture
class-design
solid
single-responsibility
Robert Harvey
źródło
źródło
Odpowiedzi:
Jednym ze sposobów obejścia tego problemu jest wyobrażenie sobie potencjalnych zmian wymagań w przyszłych projektach i zadanie sobie pytania, co trzeba zrobić, aby je zrealizować.
Na przykład:
Lub:
Chodzi o to, aby zminimalizować wpływ przyszłych potencjalnych zmian, ograniczając modyfikacje kodu do jednego obszaru kodu na obszar zmian.
Co najmniej, twoje klasy powinny oddzielić logiczne obawy od fizycznych. Wielki zestaw przykładów można znaleźć w
System.IO
przestrzeni nazw: nie możemy znaleźć różne rodzaje strumieni fizycznych (npFileStream
,MemoryStream
alboNetworkStream
) i różnych czytelników i pisarzy (BinaryWriter
,TextWriter
), które działają na poziomie logicznym. Oddzielając je w ten sposób unikamy combinatoric eksplozji: zamiast koniecznościFileStreamTextWriter
,FileStreamBinaryWriter
,NetworkStreamTextWriter
,NetworkStreamBinaryWriter
,MemoryStreamTextWriter
, iMemoryStreamBinaryWriter
, wystarczy podłączyć nagrywarkę i strumienia i można mieć, co chcesz. Następnie możemy dodać, powiedzmy,XmlWriter
bez konieczności ponownego implementowania go dla pamięci, plików i sieci osobno.źródło
Praktycznie rzecz biorąc, obowiązki ograniczają rzeczy, które mogą się zmienić. Tak więc nie ma naukowego ani formalnego sposobu osiągnięcia tego, co stanowi odpowiedzialność, niestety. To jest wyrok sądu.
Chodzi o to, co według twojego doświadczenia może się zmienić.
Stosujemy język zasady w hiperbolicznej, dosłownej, gorliwej furii. Mamy tendencję do dzielenia klas, ponieważ mogą się zmieniać lub postępować zgodnie z liniami, które po prostu pomagają nam rozwiązać problemy. (Ten drugi powód nie jest z natury zły). Ale SRP nie istnieje dla samego siebie; służy do tworzenia konserwowalnego oprogramowania.
Więc ponownie, jeśli podziały nie są napędzane przez prawdopodobne zmiany, to nie są tak naprawdę w służbie dla SRP 1, jeśli YAGNI ma większe zastosowanie. Oba służą temu samemu ostatecznemu celowi. I oba są sprawami osądu - mam nadzieję, że przyprawiony osąd.
Kiedy wujek Bob pisze o tym, sugeruje, abyśmy myśleli o „odpowiedzialności” w kategoriach „kto prosi o zmianę”. Innymi słowy, nie chcemy, aby Partia A straciła pracę, ponieważ Partia B poprosiła o zmianę.
Dobrzy i doświadczeni programiści będą mieli poczucie, które zmiany są prawdopodobne. Ta lista mentalna będzie się nieco różnić w zależności od branży i organizacji.
Co stanowi odpowiedzialność w danym zastosowaniu, w danej organizacji, jest ostatecznie kwestią doprawione wyroku. Chodzi o to, co może się zmienić. W pewnym sensie chodzi o to, kto jest właścicielem wewnętrznej logiki modułu.
1. Aby być jasnym, nie oznacza to, że są to złe podziały. Mogą to być wielkie podziały, które znacznie poprawiają czytelność kodu. Oznacza to po prostu, że nie kieruje nimi SRP.
źródło
Śledzę: „klasy powinny mieć tylko jeden powód do zmiany”.
Dla mnie oznacza to myślenie o programach typu harebrain, które może wymyślić mój właściciel produktu („Musimy wspierać telefon komórkowy!”, „Musimy przejść do chmury!”, „Musimy wspierać chiński!”). Dobre projekty ograniczą wpływ tych programów do mniejszych obszarów i uczynią je stosunkowo łatwymi do osiągnięcia. Złe projekty oznaczają przechodzenie do dużej ilości kodu i dokonywanie ryzykownych zmian.
Doświadczenie jest jedyną rzeczą, którą znalazłem, aby właściwie ocenić prawdopodobieństwo tych szalonych schematów - ponieważ ułatwienie jednego może utrudnić dwóm innym - i ocenę dobroci projektu. Doświadczeni programiści mogą wyobrazić sobie, co powinni zrobić, aby zmienić kod, co leży i ugryzie ich w tyłek oraz jakie sztuczki ułatwiają to. Doświadczeni programiści mają dobre wyczucie, jak bardzo są wkręceni, gdy właściciel produktu prosi o szalone rzeczy.
Praktycznie uważam, że testy jednostkowe są tutaj pomocne. Jeśli kod jest nieelastyczny, trudno będzie go przetestować. Jeśli nie możesz wstrzykiwać próbnych lub innych danych testowych, prawdopodobnie nie będziesz w stanie wstrzyknąć tego
SupportChinese
kodu.Innym przybliżonym miernikiem jest wysokość windy. Tradycyjne stanowiska windowe to „jeśli byłeś w windzie z inwestorem, czy możesz go sprzedać według pomysłu?”. Startupy muszą mieć proste, krótkie opisy tego, co robią - na czym się koncentrują. Podobnie, klasy (i funkcje) powinny mieć prosty opis tego, co robią . Nie „ta klasa implementuje niektóre Fubar, dzięki czemu można ich używać w tych konkretnych scenariuszach”. Coś, co możesz powiedzieć innemu deweloperowi: „Ta klasa tworzy użytkowników”. Jeśli nie można komunikować się, że do innych deweloperów, masz zamiar dostać się błędy.
źródło
Nikt nie wie. A przynajmniej nie jesteśmy w stanie uzgodnić jednej definicji. To sprawia, że SPR (i inne zasady SOLID) są dość kontrowersyjne.
Twierdziłbym, że jedną z umiejętności, których twórca oprogramowania musi się nauczyć w trakcie swojej kariery zawodowej, jest dowiedzieć się, na czym polega odpowiedzialność. Im więcej kodu piszesz i recenzujesz, tym więcej doświadczenia będziesz musiał określić, czy coś jest pojedynczą czy wielokrotną odpowiedzialnością. Lub jeśli pojedyncza odpowiedzialność jest podzielona na oddzielne części kodu.
Twierdziłbym, że głównym celem SRP nie jest twarda zasada. Ma nam przypominać o spójności kodu i zawsze podejmować świadomy wysiłek, aby ustalić, który kod jest spójny, a co nie.
źródło
Myślę, że termin „odpowiedzialność” jest użyteczny jako metafora, ponieważ pozwala nam korzystać z oprogramowania w celu sprawdzenia, jak dobrze jest ono zorganizowane. W szczególności skupiłbym się na dwóch zasadach:
Te dwie zasady pozwalają nam znacząco oddzielić odpowiedzialność, ponieważ wzajemnie się ze sobą bawią. Jeśli upoważniasz kawałek kodu do zrobienia czegoś dla siebie, musi on ponosić odpowiedzialność za to, co robi. Powoduje to odpowiedzialność, że klasa musi być w stanie urosnąć, rozszerzając to „jeden powód do zmiany” na coraz szersze zakresy. Jednak, gdy poszerzasz zakres, naturalnie zaczynasz napotykać sytuacje, w których wiele podmiotów jest odpowiedzialnych za to samo. Jest to obarczone problemami związanymi z rzeczywistą odpowiedzialnością, więc na pewno jest to również problem z kodowaniem. W rezultacie zasada ta powoduje zawężenie zakresu, gdy dzielisz odpowiedzialność na niepowielone paczki.
Oprócz tych dwóch, trzecia zasada wydaje się uzasadniona:
Rozważ świeżo wybity program ... pusta tablica. Na początku masz tylko jeden byt, którym jest program jako całość. Odpowiada za ... wszystko. Oczywiście w pewnym momencie zaczniesz delegować odpowiedzialność na funkcje lub klasy. W tym momencie obowiązują dwie pierwsze zasady, które zmuszają cię do zrównoważenia tej odpowiedzialności. Program najwyższego poziomu jest nadal odpowiedzialny za ogólną wydajność, podobnie jak kierownik odpowiada za produktywność swojego zespołu, ale każda podjednostka została delegowana do odpowiedzialności, a wraz z nią uprawnienia do wykonywania tej odpowiedzialności.
Dodatkową zaletą jest to, że SOLID jest szczególnie kompatybilny z każdym oprogramowaniem firmowym, jakie może być konieczne. Każda firma na tej planecie ma jakieś pojęcie o tym, jak przekazać odpowiedzialność i nie wszyscy się z tym zgadzają. Jeśli przekażesz odpowiedzialność za oprogramowanie w sposób przypominający delegację Twojej firmy, przyszłym programistom będzie znacznie łatwiej dostosować się do sposobu, w jaki robisz to w tej firmie.
źródło
W tej konferencji w Yale wujek Bob podaje ten zabawny przykład:
Mówi, że
Employee
ma trzy powody do zmiany, trzy źródła wymagań dotyczących zmian, i podaje to dowcipne i zuchwałe , ale ilustracyjne wyjaśnienie:Podaje to rozwiązanie, które rozwiązuje naruszenie SRP, ale musi rozwiązać naruszenie DIP, które nie jest pokazane na filmie.
źródło
Myślę, że lepszym sposobem na podzielenie rzeczy niż „powody zmiany” jest rozpoczęcie od zastanowienia się nad tym, czy sensowne byłoby wymaganie, aby kod, który musi wykonać dwie (lub więcej) akcji, musiałby zawierać osobne odniesienie do obiektu dla każdej akcji i czy użyteczny byłby obiekt publiczny, który mógłby wykonać jedną akcję, ale nie drugą.
Jeśli odpowiedzi na oba pytania są twierdzące, sugerowałoby to, że działania powinny być wykonane przez osobne klasy. Jeśli odpowiedzi na oba pytania są przeczące, sugerowałoby to, że z publicznego punktu widzenia powinna istnieć jedna klasa; jeśli kod tego byłby niewygodny, może być wewnętrznie podzielony na klasy prywatne. Jeśli odpowiedź na pierwsze pytanie brzmi „nie”, ale na drugie pytanie „tak”, dla każdej akcji powinna istnieć osobna klasa plus klasa złożona, która zawiera odniesienia do instancji innych.
Jeśli ktoś ma osobne klasy dla klawiatury kasy, brzęczyka, odczytu numerycznego, drukarki pokwitowań i szuflady kasowej, a nie ma klasy złożonej dla pełnej kasy fiskalnej, kod, który ma przetwarzać transakcję, może zostać przypadkowo wywołany w sposób, który pobiera dane z klawiatury jednego urządzenia, wytwarza hałas z sygnału dźwiękowego drugiego urządzenia, pokazuje liczby na wyświetlaczu trzeciego urządzenia, drukuje pokwitowanie na drukarce czwartego urządzenia i wysuwa szufladę kasową piątego urządzenia. Każda z tych funkcji podrzędnych może być użytecznie obsługiwana przez osobną klasę, ale powinna również istnieć klasa złożona, która do nich dołącza. Klasa złożona powinna przekazać tyle logiki klasom, ile to możliwe,
Można powiedzieć, że „odpowiedzialnością” każdej klasy jest albo włączenie jakiejś prawdziwej logiki, albo zapewnienie wspólnego punktu przyłączenia dla wielu innych klas, które to robią, ale ważne jest przede wszystkim skupienie się na tym, jak kod klienta powinien postrzegać klasę. Jeśli kod klienta ma sens widzieć coś jako pojedynczy obiekt, wówczas kod klienta powinien widzieć to jako pojedynczy obiekt.
źródło
SRP trudno jest naprawić. Jest to głównie kwestia przypisania „zadań” do twojego kodu i upewnienia się, że każda część ma jasne obowiązki. Podobnie jak w prawdziwym życiu, w niektórych przypadkach dzielenie pracy między ludzi może być całkiem naturalne, ale w innych przypadkach może być naprawdę trudne, szczególnie jeśli ich nie znasz (lub pracę).
Zawsze zalecam po prostu napisanie prostego kodu, który najpierw działa , a następnie trochę zawęzić: Po pewnym czasie zobaczysz, jak kod klastruje się naturalnie. Myślę, że błędem jest narzucanie odpowiedzialności, zanim poznasz kod (lub ludzi) i pracę do wykonania.
Jedną z rzeczy, które zauważysz, jest to, że moduł zaczyna robić za dużo i jest trudny do debugowania / utrzymania. To jest moment na refaktoryzację; jaka powinna być podstawowa praca i jakie zadania można przekazać innemu modułowi? Na przykład, czy powinien obsługiwać kontrole bezpieczeństwa i inne czynności, czy najpierw należy przeprowadzać kontrole bezpieczeństwa gdzie indziej, czy może to skomplikować kod?
Jeśli użyjesz zbyt wielu pośredników, znów stanie się bałaganem ... jeśli chodzi o inne zasady, ten będzie w konflikcie z innymi, takimi jak KISS, YAGNI itp. Wszystko jest kwestią równowagi.
źródło
„Zasada pojedynczej odpowiedzialności” może być mylącą nazwą. „Tylko jeden powód do zmiany” jest lepszym opisem zasady, ale nadal łatwo ją źle zrozumieć. Nie mówimy o tym, co powoduje zmianę stanu obiektów w czasie wykonywania. Zastanawiamy się, co może spowodować, że programiści będą musieli zmienić kod w przyszłości.
O ile nie naprawimy błędu, zmiana będzie spowodowana nowym lub zmienionym wymaganiem biznesowym. Będziesz musiał myśleć poza samym kodem i wyobrażać sobie, jakie czynniki zewnętrzne mogą spowodować, że wymagania zmienią się niezależnie . Mówić:
Idealnie chcesz, aby niezależne czynniki wpływały na różne klasy. Np. Ponieważ stawki podatkowe zmieniają się niezależnie od nazw produktów, zmiany nie powinny wpływać na te same klasy. W przeciwnym razie ryzykujesz wprowadzeniem zmiany podatkowej, błędem w nazwie produktu, który jest rodzajem ścisłego połączenia, którego chcesz uniknąć w systemie modułowym.
Nie skupiaj się więc na tym, co może się zmienić - wszystko może się zmienić w przyszłości. Skoncentruj się na tym, co może się niezależnie zmienić. Zmiany są zwykle niezależne, jeśli są spowodowane przez różnych aktorów.
Twój przykład z tytułami pracy jest na dobrej drodze, ale powinieneś wziąć to dosłownie! Jeśli marketing może powodować zmiany w kodzie, a finanse mogą powodować inne zmiany, zmiany te nie powinny wpływać na ten sam kod, ponieważ są to dosłownie różne tytuły stanowisk, a zatem zmiany będą dokonywane niezależnie.
Cytując wuja Boba, który wynalazł ten termin:
Podsumowując: „odpowiedzialność” odpowiada jednej funkcji biznesowej. Jeśli więcej niż jeden aktor może spowodować zmianę klasy, klasa prawdopodobnie łamie tę zasadę.
źródło
Dobry artykuł, który wyjaśnia zasady programowania SOLID i podaje przykłady kodu zarówno przestrzegającego, jak i nie przestrzegającego tych zasad, to https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- projekt .
W przykładzie dotyczącym SRP podaje przykład kilku klas kształtów (okrąg i kwadrat) i klasy zaprojektowanej do obliczania całkowitej powierzchni wielu kształtów.
W swoim pierwszym przykładzie tworzy klasę obliczającą obszar i zwraca jej wynik jako HTML. Później decyduje, że zamiast tego chce wyświetlić JSON i musi zmienić klasę obliczania obszaru.
Problem z tym przykładem polega na tym, że jego klasa obliczania powierzchni jest odpowiedzialna za obliczanie powierzchni kształtów ORAZ wyświetlanie tego obszaru. Następnie przechodzi lepszy sposób, aby to zrobić, używając innej klasy zaprojektowanej specjalnie do wyświetlania obszarów.
Jest to prosty przykład (i łatwiejsze do zrozumienia czytanie artykułu, ponieważ zawiera fragmenty kodu), ale pokazuje podstawową ideę SRP.
źródło
Przede wszystkim masz dwa osobne problemy: problem z metodami, które należy umieścić w swoich klasach, oraz problem z wzdęcia interfejsu.
Interfejsy
Masz ten interfejs:
Można przypuszczać, że masz wiele klas zgodnych z
CustomerCRUD
interfejsem (w przeciwnym razie interfejs jest niepotrzebny) oraz niektóre funkcje,do_crud(customer: CustomerCRUD)
które przyjmują zgodny obiekt. Ale już złamałeś SRP: powiązałeś te cztery różne operacje razem.Powiedzmy, że później będziesz działał na widokach bazy danych. Widok bazy danych ma tylko ten
Read
sposób dostępne dla niego. Ale chcesz napisać funkcję,do_query_stuff(customer: ???)
która operuje transparentnie na pełnych tabelach lub widokach; w końcu używa tylko tejRead
metody.Utwórz interfejs
public Interface CustomerReader {public Customer Read (customerID: int)}
i uwzględnij swój
CustomerCrud
interfejs jako:Ale nie ma końca w zasięgu wzroku. Mogą istnieć obiekty, które możemy tworzyć, ale nie aktualizować itp. Ta królicza nora jest zbyt głęboka. Jedynym rozsądnym sposobem na przestrzeganie zasady pojedynczej odpowiedzialności jest sprawienie, aby wszystkie interfejsy zawierały dokładnie jedną metodę . Go faktycznie postępuje zgodnie z tą metodologią z tego, co widziałem, z ogromną większością interfejsów zawierających jedną funkcję; jeśli chcesz określić interfejs, który zawiera dwie funkcje, musisz niezręcznie utworzyć nowy interfejs, który łączy te dwie funkcje. Wkrótce otrzymasz kombinatoryczną eksplozję interfejsów.
Wyjściem z tego bałaganu jest zastosowanie podsieci strukturalnych (zaimplementowanych np. W OCaml) zamiast interfejsów (które są formą nominalnego podtypu). Nie definiujemy interfejsów; zamiast tego możemy po prostu napisać funkcję
to wywołuje dowolne metody, które lubimy. OCaml użyje wnioskowania typu, aby ustalić, że możemy przekazać dowolny obiekt, który implementuje te metody. W tym przykładzie byłoby ustalone, że
customer
ma typ<read: int -> unit, update: int -> unit, ...>
.Klasy
To rozwiązuje bałagan interfejsu ; ale nadal musimy wdrożyć klasy, które zawierają wiele metod. Na przykład, czy powinniśmy stworzyć dwie różne klasy
CustomerReader
iCustomerWriter
? Co jeśli chcemy zmienić sposób odczytywania tabel (np. Teraz buforujemy nasze odpowiedzi w redis przed pobraniem danych), ale teraz jak są one zapisywane? Jeśli podążysz za tym ciągiem rozumowania do jego logicznej konkluzji, jesteś nierozerwalnie związany z programowaniem funkcjonalnym :)źródło
Moim zdaniem najbliższą SRP, która przychodzi mi do głowy, jest przepływ użytkowania. Jeśli nie masz wyraźnego przepływu dla danej klasy, prawdopodobnie twoja klasa ma zapach projektowy.
Przepływ użytkowania byłby daną kolejnością wywołań metod, która dałaby oczekiwany (a zatem testowalny) wynik. Zasadniczo definiujesz klasę za pomocą przypadków użycia, które otrzymała IMHO, dlatego cała metodologia programu skupia się na interfejsach nad implementacją.
źródło
Ma to na celu osiągnięcie wielu zmian wymagań, nie wymaga zmiany komponentu .
Ale powodzenia, rozumiejąc to na pierwszy rzut oka, kiedy po raz pierwszy usłyszysz o SOLID.
Widzę wiele komentarzy, które mówią, że SRP i YAGNI mogą być ze sobą sprzeczne, ale YAGN narzucony przez TDD (GOOS, London School) nauczył mnie myśleć i projektować moje komponenty z perspektywy klienta. Zacząłem projektować moje interfejsy w taki sposób, aby najmniej klient tego chciał, czyli jak mało powinien . I to ćwiczenie można wykonać bez znajomości TDD.
Podoba mi się technika opisana przez wuja Boba (niestety nie pamiętam skąd), która idzie w stylu:
Ta technika jest absolutna i, jak powiedział @svidgen, SRP jest wezwaniem do osądu, ale kiedy uczysz się czegoś nowego, absolutne są najlepsze, łatwiej jest po prostu zawsze coś zrobić. Upewnij się, że powodem, dla którego się nie rozdzieliłeś, jest; wykształcone oszacowanie, a nie dlatego, że nie wiesz jak. To jest sztuka i wymaga doświadczenia.
Wydaje mi się, że wiele odpowiedzi wydaje się stanowić argument za oddzieleniem płatności od produkcji w przypadku SRP .
SRP jest nie do upewnij się, że zmiana nie propagować dół wykres zależności.
Teoretycznie bez SRP nie byłoby żadnych zależności ...
Jedna zmiana nie powinna powodować zmiany w wielu miejscach aplikacji, ale mamy inne zasady. SRP poprawia jednak otwartą zasadę zamkniętą . Ta zasada dotyczy bardziej abstrakcji, jednak mniejsze abstrakty są łatwiejsze do reimplementacji .
Dlatego ucząc SOLID jako całości, uważaj, aby nauczyć się, że SRP pozwala zmieniać mniej kodu, gdy zmieniają się wymagania, podczas gdy w rzeczywistości pozwala ci pisać mniej nowego kodu.
źródło
When learning something new, absolutes are the best, it is easier to just always do something.
- Z mojego doświadczenia wynika, że nowi programiści są zbyt dogmatyczni. Absolutyzm prowadzi do niemądrych programistów i programowania kultowego. Mówiąc „po prostu to zrobić” jest w porządku, tak długo, jak można zrozumieć, że osoba, z którą rozmawiasz będzie musiał później oduczyć czego nauczyły je.Nie ma na to jednoznacznej odpowiedzi. Chociaż pytanie jest wąskie, wyjaśnienia nie są.
Dla mnie jest to coś takiego jak Razam Razor, jeśli chcesz. To ideał, w którym próbuję zmierzyć mój obecny kod. Trudno to wyrazić prostymi i prostymi słowami. Inną metaforą byłby »jeden temat«, który jest tak abstrakcyjny, tzn. Trudny do zrozumienia, jak »jedna odpowiedzialność«. Trzecim opisem byłoby „zajmowanie się jednym poziomem abstrakcji”.
Co to znaczy praktycznie?
Ostatnio używam stylu kodowania, który składa się głównie z dwóch faz:
Fazę I najlepiej opisać jako twórczy chaos. W tej fazie piszę kod, gdy myśli płyną - tj. Surowy i brzydki.
Faza II jest całkowitym przeciwieństwem. To jest jak sprzątanie po huraganie. To wymaga najwięcej pracy i dyscypliny. A potem patrzę na kod z perspektywy projektanta.
Obecnie pracuję głównie w Pythonie, co pozwala mi później myśleć o obiektach i klasach. Pierwsza faza I - piszę tylko funkcje i rozkładam je prawie losowo w różnych modułach. W fazie II , kiedy już wszystko zaczęło działać, przyjrzałem się bliżej modułowi, który dotyczy danej części rozwiązania. A gdy przeglądam moduły, pojawiają się dla mnie tematy . Niektóre funkcje są powiązane tematycznie. To są dobrzy kandydaci na zajęcia . A po tym, jak zamieniłem funkcje w klasy - co jest prawie gotowe z wcięciem i dodaniem
self
do listy parametrów w pythonie;) - używamSRP
jak Razor Razor do rozszyfrowania funkcjonalności innych modułów i klas.Obecnym przykładem może być pisanie niewielkiej funkcjonalności eksportu innego dnia.
Potrzebny był plik CSV , Excel i połączone arkusze Excela w zipie.
Zwykła funkcjonalność została wykonana w trzech widokach (= funkcje). Każda funkcja wykorzystywała wspólną metodę określania filtrów i drugą metodę odzyskiwania danych. Następnie w każdej funkcji miało miejsce przygotowanie eksportu i zostało dostarczone jako odpowiedź z serwera.
Wymieszano zbyt wiele poziomów abstrakcji:
I) obsługa przychodzących / wychodzących wniosków / odpowiedzi
II) określanie filtrów
III) odzyskiwanie danych
IV) transformacja danych
Łatwym krokiem było użycie jednej abstrakcji (
exporter
) do zajęcia się warstwami II-IV w pierwszym kroku.Pozostał tylko temat dotyczący zapytań / odpowiedzi . Na tym samym poziomie abstrakcji jest wydobywanie parametrów żądania, co jest w porządku. Miałem więc za ten pogląd jedną „odpowiedzialność”.
Po drugie, musiałem rozbić eksportera, który, jak widzieliśmy, składał się z co najmniej trzech innych warstw abstrakcji.
Określanie kryteriów filtrowania i faktyczne wycofywanie jest prawie na tym samym poziomie abstrakcji (filtry są potrzebne, aby uzyskać odpowiedni podzbiór danych). Poziomy te zostały umieszczone w czymś w rodzaju warstwy dostępu do danych .
W następnym kroku podzieliłem rzeczywiste mechanizmy eksportu: tam, gdzie potrzebne było zapisywanie do pliku tymczasowego, podzieliłem to na dwie „obowiązki”: jedną do faktycznego zapisu danych na dysk i drugą część, która dotyczyła faktycznego formatu.
Wraz z tworzeniem się klas i modułów stało się jasne, co było gdzie. I zawsze ukryte pytanie, czy klasa robi za dużo .
Trudno podać przepis do naśladowania. Oczywiście mógłbym powtórzyć tajemniczą »jeden poziom abstrakcji« - zasadę, jeśli to pomoże.
Przeważnie dla mnie jest to rodzaj „artystycznej intuicji”, która prowadzi do obecnego projektu; Modeluję kod tak, jak artysta może rzeźbić glinę lub malować.
Wyobraź sobie mnie jako kodującego Boba Rossa ;)
źródło
Co próbuję zrobić, aby napisać kod zgodny z SRP:
Przykład:
Problem: pobierz dwie liczby od użytkownika, oblicz ich sumę i wyślij wynik użytkownikowi:
Następnie spróbuj zdefiniować obowiązki na podstawie zadań, które należy wykonać. Z tego wyodrębnij odpowiednie klasy:
Następnie refaktoryzowany program staje się:
Uwaga: ten bardzo prosty przykład uwzględnia tylko zasadę SRP. Zastosowanie innych zasad (np. Kod „L” powinien zależeć raczej od abstrakcji niż konkrecji) zapewniłoby kodowi więcej korzyści i uczyniłoby go łatwiejszym w przypadku zmian biznesowych.
źródło
Z książki Roberta C. Martinsa Clean Architecture: A Craftsman's Guide to Software Structure and Design , opublikowanej 10 września 2017 r., Robert pisze na stronie 62, co następuje:
Więc nie chodzi o kod. SRP polega na kontrolowaniu przepływu wymagań i potrzeb biznesowych, które mogą pochodzić tylko z jednego źródła.
źródło