Zasada Jednej Odpowiedzialności stanowi, że klasa powinna zrobić jedną i tylko jedną rzecz. Niektóre przypadki są dość wyraźne. Inne są jednak trudne, ponieważ to, co wygląda na „jedną rzecz”, gdy jest oglądane na danym poziomie abstrakcji, może być wieloma rzeczami, gdy patrzy się na niższym poziomie. Obawiam się również, że jeśli zasada niższej odpowiedzialności będzie przestrzegana na niższych poziomach, może to spowodować nadmiernie oddzielony, pełny kod ravioli, w którym wydano więcej linii, tworząc małe klasy dla wszystkiego i gromadząc informacje wokół, niż faktycznie rozwiązując dany problem.
Jak opisałbyś, co oznacza „jedna rzecz”? Jakie są konkretne oznaki, że klasa naprawdę robi więcej niż „jedną rzecz”?
design
object-oriented
dsimcha
źródło
źródło
Odpowiedzi:
Bardzo podoba mi się sposób, w jaki Robert C. Martin (wujek Bob) przekształca zasadę pojedynczej odpowiedzialności (link do pliku PDF) :
Różni się nieco od tradycyjnej definicji „powinien robić tylko jedną rzecz” i podoba mi się to, ponieważ zmusza cię do zmiany sposobu myślenia o swojej klasie. Zamiast myśleć o „czy to robi jedną rzecz?”, Zamiast tego zastanawiasz się, co można zmienić i jak zmiany te wpływają na twoją klasę. Na przykład, jeśli zmienia się baza danych, czy Twoja klasa musi się zmienić? Co jeśli zmieni się urządzenie wyjściowe (na przykład ekran, urządzenie mobilne lub drukarka)? Jeśli twoja klasa musi się zmienić z powodu zmian z wielu innych kierunków, oznacza to, że twoja klasa ma zbyt wiele obowiązków.
W powiązanym artykule wujek Bob podsumowuje:
źródło
Wciąż zadaję sobie pytanie, jaki problem SRP próbuje rozwiązać? Kiedy SRP mi pomaga? Oto, co wymyśliłem:
Powinieneś refaktoryzować odpowiedzialność / funkcjonalność poza klasę, gdy:
1) Zduplikowano funkcjonalność (DRY)
2) Stwierdzasz, że Twój kod wymaga innego poziomu abstrakcji, aby pomóc Ci go zrozumieć (KISS)
3) Odkrywasz, że eksperci w dziedzinie rozumieją elementy funkcjonalności jako oddzielne od innego komponentu (język wszechobecny)
NIE POWINNYŚ refaktoryzować odpowiedzialności poza klasę, gdy:
1) Nie ma żadnej zduplikowanej funkcjonalności.
2) Ta funkcjonalność nie ma sensu poza kontekstem twojej klasy. Innymi słowy, twoja klasa zapewnia kontekst, w którym łatwiej jest zrozumieć funkcjonalność.
3) Eksperci w Twojej dziedzinie nie mają pojęcia o tej odpowiedzialności.
Przyszło mi do głowy, że jeśli SRP ma szerokie zastosowanie, handlujemy jednym rodzajem złożoności (próbując tworzyć głowy lub ogony klasy o wiele za dużo dzieje się w środku) z innym rodzajem (starając się utrzymać wszystkich współpracowników / poziomy abstrakcja prosto, aby dowiedzieć się, co faktycznie robią te klasy).
W razie wątpliwości pomiń to! Zawsze możesz refaktoryzować później, gdy jest ku temu wyraźny argument.
Co myślisz?
źródło
Nie wiem, czy istnieje obiektywna skala, ale tym, co by to ujawniło, byłyby metody - nie tyle ich liczba, ile różnorodność ich funkcji. Zgadzam się, że możesz posunąć się za daleko, ale nie przestrzegałbym żadnych twardych i szybkich zasad w tym zakresie.
źródło
Odpowiedź leży w definicji
To, co definiujesz jako odpowiedzialność, ostatecznie wyznacza granicę.
Przykład:
Mam komponent, który jest odpowiedzialny za wyświetlanie faktur -> w tym przypadku, jeśli zacznę dodawać coś więcej, to łamię zasadę.
Z drugiej strony, jeśli powiedziałbym, że jestem odpowiedzialny za obsługę faktur -> dodanie wielu mniejszych funkcji (np. Drukowanie faktury , aktualizacja faktury ) wszystkie mieszczą się w tej granicy.
Gdyby jednak ten moduł zaczął obsługiwać dowolną funkcjonalność poza fakturami , byłby poza tą granicą.
źródło
Zawsze oglądam to na dwóch poziomach:
Więc coś w rodzaju obiektu domeny o nazwie Dog:
Dog
jest moja klasa, ale Psy potrafią robić wiele rzeczy! Mogę mieć metod, takich jakwalk()
,run()
ibite(DotNetDeveloper spawnOfBill)
(Niestety nie może oprzeć się, p).Jeśli stanie
Dog
się nieporęczny, pomyślę o tym, jak grupy tych metod mogą być modelowane razem w innej klasie, takiej jakMovement
klasa, która może zawierać mojewalk()
irun()
metody.Nie ma twardej i szybkiej reguły, twój projekt OO będzie ewoluował z czasem. Staram się korzystać z przejrzystego interfejsu / publicznego interfejsu API, a także prostych metod, które robią jedną i drugą rzecz dobrze.
źródło
Patrzę na to bardziej zgodnie z klasą, powinna reprezentować tylko jedną rzecz. Przywłaszczyć @ przykład Karianna za, mam
Dog
klasę, która ma metodywalk()
,run()
ibark()
. I nie zamierzam dodać metodymeaow()
,squeak()
,slither()
lubfly()
ponieważ nie są to rzeczy, które psy robić. Są to rzeczy, które robią inne zwierzęta, a te inne zwierzęta miałyby swoje własne klasy reprezentujące je.(BTW, jeśli pies ma latać, to prawdopodobnie powinien zatrzymać wyrzucanie go z okna).
źródło
SeesFood
za cechęDogEyes
,Bark
jak coś zrobionego przezDogVoice
, iEat
jak coś zrobionego przezDogMouth
, wtedy logikaif (dog.SeesFood) dog.Eat(); else dog.Bark();
stanie sięif (eyes.SeesFood) mouth.Eat(); else voice.Bark();
, tracąc jakiekolwiek poczucie tożsamości, że oczy, usta i głos są powiązane z jednym uprawnieniem.Dog
klasy, to prawdopodobnie jest onDog
powiązany. Jeśli nie, prawdopodobnie skończyłbyś z czymś takim,myDog.Eyes.SeesFood
a nie tylkoeyes.SeesFood
. Inną możliwością jestDog
udostępnienieISee
interfejsu wymagającegoDog.Eyes
właściwości iSeesFood
metody.Eye
klasę, ale pies powinien „widzieć” używając jego oczu, a nie tylko oczu, które mogą widzieć. Pies nie jest okiem, ale też nie jest po prostu oprawą oka. Jest to „rzecz, która może [przynajmniej spróbować] zobaczyć” i jako taka powinna zostać opisana za pomocą interfejsu. Nawet ślepego psa można zapytać, czy widzi jedzenie; nie będzie to bardzo przydatne, ponieważ pies zawsze powie „nie”, ale nie ma nic złego w pytaniu.Klasa powinna zrobić jedną rzecz, patrząc na swój własny poziom abstrakcji. Bez wątpienia zrobi wiele rzeczy na mniej abstrakcyjnym poziomie. W ten sposób działają klasy, aby programy były łatwiejsze w utrzymaniu: ukrywają szczegóły implementacji, jeśli nie trzeba ich dokładnie badać.
Używam do tego nazw klas. Jeśli nie mogę nadać klasie dość krótkiej nazwy opisowej lub jeśli w nazwie tej znajduje się słowo „I”, prawdopodobnie naruszam zasadę pojedynczej odpowiedzialności.
Z mojego doświadczenia wynika, że łatwiej jest utrzymać tę zasadę na niższych poziomach, gdzie rzeczy są bardziej konkretne.
źródło
Chodzi o posiadanie jednego wyjątkowego rola .
Każda klasa powinna zostać wznowiona nazwą roli. Rola to w rzeczywistości (zestaw) czasowników powiązanych z kontekstem.
Na przykład :
Plik zapewnia dostęp do pliku. FileManager zarządza obiektami File.
Dane wstrzymania zasobów dla jednego zasobu z pliku. ResourceManager wstrzymuje i udostępnia wszystkie zasoby.
Tutaj możesz zobaczyć, że niektóre czasowniki, takie jak „zarządzaj”, oznaczają zestaw innych czasowników. Same czasowniki są przez większość czasu lepiej uważane za funkcje niż klasy. Jeśli czasownik implikuje zbyt wiele działań, które mają swój wspólny kontekst, to powinien być klasą samą w sobie.
Chodzi więc o to, abyś miał proste wyobrażenie o tym, co robi klasa, poprzez zdefiniowanie unikalnej roli, która może być agregacją kilku podgrup (wykonywanych przez obiekty składowe lub inne obiekty).
Często buduję klasy menedżerów, które zawierają kilka różnych innych klas. Jak fabryka, rejestr itp. Zobacz klasę menedżera, taką jak szef grupy, szef orkiestry, który poprowadzi inne ludy do współpracy, aby osiągnąć pomysł na wysokim poziomie. Ma jedną rolę, ale implikuje pracę z innymi unikalnymi rolami w środku. Możesz także zobaczyć, jak jest zorganizowana firma: dyrektor generalny nie jest produktywny na poziomie czystej wydajności, ale jeśli go nie ma, nic nie może działać poprawnie razem. To jego rola.
Podczas projektowania określ unikalne role. I dla każdej roli ponownie sprawdź, czy nie można jej rozdzielić na kilka innych ról. W ten sposób, jeśli chcesz w prosty sposób zmienić sposób, w jaki Twój Menedżer buduje obiekty, po prostu zmień Fabrykę i idź spokojnie.
źródło
SRP to nie tylko podział klas, ale także delegowanie funkcjonalności.
W powyższym przykładzie Dog nie używaj SRP jako uzasadnienia posiadania 3 oddzielnych klas, takich jak DogBarker, DogWalker itp. (Niska kohezja). Zamiast tego spójrz na implementację metod klasy i zdecyduj, czy „wiedzą za dużo”. Nadal możesz mieć funkcję dog.walk (), ale prawdopodobnie metoda walk () powinna przekazać innej klasie szczegółowe informacje o sposobie chodzenia.
W efekcie pozwalamy klasie psów mieć jeden powód do zmiany: ponieważ zmieniają się psy. Oczywiście, łącząc to z innymi SOLIDNYMI zasadami, rozszerzyłbyś Psa o nową funkcjonalność zamiast zmieniać Psa (otwarty / zamknięty). I wstrzyknąłbyś swoje zależności, takie jak IMove i IEat. I oczywiście stworzyłbyś te oddzielne interfejsy (segregacja interfejsów). Pies zmieniłby się tylko, gdybyśmy znaleźli błąd lub gdyby psy uległy zasadniczej zmianie (Liskov Sub, nie przedłużaj, a następnie usuwaj zachowanie).
Efektem netto SOLID jest to, że możemy pisać nowy kod częściej niż musimy modyfikować istniejący kod, i to jest duża wygrana.
źródło
Wszystko zależy od definicji odpowiedzialności i tego, jak ta definicja wpłynie na utrzymanie twojego kodu. Wszystko sprowadza się do jednej rzeczy i właśnie w ten sposób twój projekt pomoże ci utrzymać kod.
I jak ktoś powiedział: „Chodzenie po wodzie i projektowanie oprogramowania na podstawie podanych specyfikacji jest łatwe, pod warunkiem, że oba są zamrożone”.
Jeśli więc definiujemy odpowiedzialność w bardziej konkretny sposób, nie musimy jej zmieniać.
Czasami obowiązki będą rażąco oczywiste, ale czasem mogą być subtelne i musimy rozsądnie zdecydować.
Załóżmy, że dodajemy kolejną odpowiedzialność do klasy Dog o nazwie catchThief (). Teraz może to prowadzić do dodatkowej różnej odpowiedzialności. Jutro, jeśli sposób, w jaki Pies łapie złodzieja, musi zostać zmieniony przez Departament Policji, klasa psów będzie musiała zostać zmieniona. W tym przypadku lepiej byłoby utworzyć kolejną podklasę i nazwać ją ThiefCathcerDog. Ale w innym ujęciu, jeśli jesteśmy pewni, że nie zmieni się to w żadnych okolicznościach lub sposób, w jaki został wdrożony catchThief, zależy od jakiegoś zewnętrznego parametru, to jest całkowicie w porządku, aby ponosić tę odpowiedzialność. Jeśli obowiązki nie są uderzająco dziwne, musimy podjąć rozsądną decyzję na podstawie przypadku użycia.
źródło
„Jeden powód do zmiany” zależy od tego, kto korzysta z systemu. Upewnij się, że masz przypadki użycia dla każdego aktora, i zrób listę najbardziej prawdopodobnych zmian, dla każdej możliwej zmiany przypadku użycia upewnij się, że zmiana wpłynie tylko na jedną klasę. Jeśli dodajesz zupełnie nowy przypadek użycia, upewnij się, że będziesz musiał jedynie rozszerzyć klasę, aby to zrobić.
źródło