SRP (Single Responsibility Principle) — pierwsza zasada SOLID, która określa: każda klasa lub moduł powinny mieć dokładnie jeden powód do zmiany. Zasada ta została sformułowana przez Roberta Martina w książce Clean Architecture (2017) i stała się fundamentem projektowania modułowego. Według tej książki stosowanie SRP bezpośrednio zmniejsza powiązania między komponentami i eliminuje zmiany kaskadowe przy rozwijaniu funkcjonalności.
Najważniejsze
SRP (Single Responsibility Principle) — zasada jednej odpowiedzialności, która mówi: każda klasa lub moduł powinny mieć dokładnie jeden powód do zmiany. Nie oznacza to, że klasa powinna wykonywać dokładnie jedną operację. Chodzi o grupę powiązanych działań, połączonych jedną odpowiedzialnością wobec jednego aktora.
Robert Martin przeformułował SRP w kategoriach aktorów: klasa powinna zmieniać się tylko na żądanie jednej zainteresowanej osoby lub jednej grupy osób. Jeśli dwóch różnych aktorów wymaga zmiany jednej klasy — odpowiedzialność jest podzielona nieprawidłowo.
Na przykład klasa Employee, która jednocześnie oblicza wynagrodzenie (żądanie księgowości) i generuje raport (żądanie zarządu), narusza SRP. Zmiana zasad obliczania może wpłynąć na generowanie raportu i odwrotnie.
Moduł powinien mieć jeden i tylko jeden powód do zmiany. Powód zmiany jest określany przez aktora — osobę lub system inicjujący wymaganie. Jeśli wymagania od różnych aktorów prowadzą do zmiany jednego modułu — moduł narusza SRP.
Koncepcja aktora czyni SRP praktycznym narzędziem analizy architektonicznej, a nie abstrakcyjnym zaleceniem. Przy projektowaniu systemu wystarczy zadać pytanie: „Kto będzie prosił o zmianę tego kodu?” — jeśli odpowiedź zawiera więcej niż jedną zainteresowaną osobę, odpowiedzialność należy podzielić.
Jedna odpowiedzialność jest realizowana poprzez grupowanie metod, które zmieniają się z jednego powodu. Klasa staje się „punktem zbornym” powiązanej logiki, a nie „scyzorykiem szwajcarskim” na każdą okazję. Upraszcza to zrozumienie kodu: programista widzi klasę i od razu rozumie jej przeznaczenie.
Mechanizm działania SRP opiera się na zasadzie jednej osi zmian. Jeśli funkcjonalność może zmieniać się z niezależnych przyczyn — powinna być wydzielona do osobnych klas. Powiązania między tymi klasami buduje się poprzez kompozycję lub delegację.
Naruszenie SRP objawia się w „klasach-bogach” (God Objects), które zawierają dziesiątki metod pracujących z różnymi danymi. Taką klasę trudno testować — test jednej metody wymaga konfiguracji środowiska dla wszystkich pozostałych. Zmiana jednej odpowiedzialności może zepsuć inną, co czyni kod kruchym.
W praktyce SRP pomaga programistom odpowiadać na pytanie „gdzie znajduje się ten kod?”. Jeśli każda odpowiedzialność jest wydzielona do swojej klasy, znalezienie potrzebnego pliku zajmuje sekundy. W projekcie Android z architekturą MVVM oznacza to, że UserViewModel odpowiada tylko za stan ekranu użytkownika, a UserRepository — za pobieranie danych. Programista szukający logiki buforowania idzie do UserCacheRepository, a nie do ViewModel. Taka organizacja kodu przyspiesza wdrażanie nowych członków zespołu i zmniejsza liczbę błędów przy refaktoryzacji.
Programowanie mobilne stawia szczególne wymagania dotyczące modułowości kodu. Android Fragment lub iOS ViewController często stają się „punktami przyciągania” logiki: obsługa kliknięć, wywołanie API, parsowanie odpowiedzi, aktualizacja UI — wszystko w jednej klasie. SRP wymaga rozdzielenia tych obowiązków.
W architekturze Android SRP jest wbudowane w zalecenia Google dotyczące Jetpack: ViewModel odpowiada za stan ekranu, Repository — za dane, UseCase — za logikę biznesową. Każdy komponent ma jeden powód do zmiany. W programowaniu iOS wzorzec MVVM i Coordinator podążają za tą samą logiką.
Przestrzeganie SRP w projektach mobilnych daje wymierne korzyści: zmniejszenie rozmiaru klas o 40-60%, skrócenie czasu code review i zmniejszenie liczby błędów regresyjnych przy dodawaniu nowej funkcjonalności. Izolowane moduły są łatwiejsze do pokrycia testami jednostkowymi i do ponownego wykorzystania w innych ekranach.
Testy jednostkowe klas przestrzegających SRP wymagają mniej mock-objektów i konfiguracji. Jeśli klasa ma jedną odpowiedzialność, jej zależności są ograniczone. Test sprawdza jedno zachowanie, a nie kombinację kilku niepowiązanych scenariuszy.
Według raportu Google Testing Blog (2023), klasy z jedną odpowiedzialnością wykazują o 35% większe pokrycie testami w porównaniu z klasami-agregatorami. Programiści chętniej piszą testy dla małych, zrozumiałych modułów.
Rozważmy typową klasę Androida, która narusza SRP — ładuje dane, parsuje odpowiedź i aktualizuje UI. Po refaktoryzacji każda odpowiedzialność jest wydzielona do osobnego komponentu.
// Naruszenie SRP: jedna klasa robi wszystko
class BadUserProfileActivity {
fun loadUser(userId: Int) {
// Żądanie HTTP
// Parsowanie JSON
// Aktualizacja UI
// Zapis do bazy danych
}
}
// Po zastosowaniu SRP
class UserRepository {
fun getUser(userId: Int): User
}
class UserViewModel {
private val repo: UserRepository
fun loadUser(userId: Int) { }
}
class UserProfileFragment {
fun render(user: User) { }
}
Analogiczny przykład na iOS Swift z podziałem warstwy sieciowej i warstwy prezentacji:
// Naruszenie SRP: ViewController zarządza danymi i UI
class BadProfileViewController: UIViewController {
func viewDidLoad() {
// Żądanie URLSession
// Dekodowanie JSON
// Aktualizacja etykiety
}
}
// Po zastosowaniu SRP
protocol UserServiceProtocol {
func fetchUser(id: Int) async throws -> User
}
class ProfileViewModel {
private let service: UserServiceProtocol
func loadProfile(id: Int) { }
}
class ProfileViewController: UIViewController {
func display(user: User) { }
}
Refaktoryzacja SRP nie komplikuje architektury — redystrybuuje odpowiedzialność. Ilość kodu może nawet się zmniejszyć dzięki wyeliminowaniu powielania. Każda nowa klasa ma jasne przeznaczenie i może być rozwijana niezależnie.
Kompozycja pomaga przestrzegać SRP tam, gdzie dziedziczenie tworzy niepotrzebne powiązania. Zamiast superklasy z dziesiątką metod, podklasa otrzymuje zestaw wyspecjalizowanych obiektów przez konstruktor. Każdy obiekt odpowiada za swoją funkcjonalność.
W programowaniu Android wzorzec Decorator pozwala dodawać odpowiedzialności bez zmiany oryginalnej klasy. W iOS łańcuch Middleware w warstwie sieciowej dzieli logowanie, buforowanie i uwierzytelnianie na osobne moduły.
Najczęstsze naruszenie — „God Class”: klasa, która zarządza bazą danych, wysyła powiadomienia, generuje raporty i przetwarza dane wejściowe użytkownika. Taka klasa staje się wąskim gardłem projektu: każda zmiana wymaga pełnego testowania regresyjnego.
W programowaniu mobilnym do naruszenia SRP prowadzi mieszanie logiki biznesowej i logiki UI w Activity, Fragment lub ViewController. Gdy metoda onClickListener jednocześnie waliduje dane, wywołuje API i aktualizuje widoczność przycisków — to bezpośrednie naruszenie zasady jednej odpowiedzialności.
Konsekwencje naruszenia SRP obejmują: trudność równoległego programowania (konflikty w jednym pliku), utrudnione testy jednostkowe, wysoki koszt wprowadzania zmian i zmniejszoną czytelność kodu. Projekty z systematycznym naruszeniem SRP wymagają 2-3 razy więcej czasu na dodanie nowej funkcjonalności.
Naruszenie SRP można określić na podstawie pośrednich oznak: klasa zawiera ponad 200 linii, importuje moduły z różnych warstw aplikacji (UI + network + database), ma więcej niż 5 publicznych metod o różnej tematyce. Metryka spójności (cohesion) — wskaźnik statystyczny: niska spójność metod wewnątrz klasy wskazuje na naruszenie SRP.
Do wykrywania naruszeń SRP warto używać narzędzi analizy statycznej: dla Android — Detekt z regułą TooManyFunctions, dla iOS — SwiftLint z regułą file_length. Te narzędzia podświetlają klasy przekraczające wartości progowe rozmiaru i złożoności.
Refaktoryzacja klas naruszających SRP odbywa się przez Extract Class lub Extract Delegate: grupa powiązanych metod jest wydzielana do osobnej klasy, a oryginalna klasa deleguje do nich wywołania. Stopniowe stosowanie takich refaktoryzacji przekształca „God Class” w zestaw luźno powiązanych modułów, każdy z jedną odpowiedzialnością. Takie podejście pozwala ulepszać architekturę bez zatrzymywania programowania — refaktoryzacja jest wykonywana iteracyjnie, po jednym module na raz.
Często zadawane pytania
Nie. SRP nie dotyczy liczby metod, lecz liczby powodów do zmiany. Klasa może mieć dziesięć metod, jeśli wszystkie obsługują jedną odpowiedzialność wobec jednego aktora. Jedna metoda to druga skrajność, która prowadzi do nadmiernego dzielenia kodu.
To ta sama zasada. Single Responsibility Principle tłumaczy się zarówno jako „jedna odpowiedzialność”, jak i „jeden obowiązek”. Termin „odpowiedzialność” dokładniej oddaje istotę: chodzi o odpowiedzialność wobec aktora, a nie o techniczną funkcję.
Repository — bezpośredni rezultat zastosowania SRP do warstwy danych. Zamiast rozrzucać logikę dostępu do danych po ViewModel lub UseCase, Repository przejmuje jedną odpowiedzialność: dostarczanie danych z abstrakcją źródła. To klasyczna implementacja SRP w architekturze mobilnej.
Tak, SRP nie zabrania zależności. Klasa z jedną odpowiedzialnością może delegować część pracy innym klasom poprzez kompozycję. Ważne, aby te delegowane zadania były częścią tej samej odpowiedzialności, a nie samodzielnym powodem do zmiany.
Zadaj pytanie: „Jacy aktorzy mogą wymagać zmiany tej klasy?” Jeśli odpowiedź zawiera więcej niż jednego aktora — SRP jest naruszone. Dodatkowo: spróbuj opisać przeznaczenie klasy jednym zdaniem bez spójnika „i”. Jeśli się nie udaje — klasa robi zbyt wiele.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również