Skip to main content

Command Palette

Search for a command to run...

SRP - Reguła Jednej Odpowiedzialności

Updated
4 min read
SRP - Reguła Jednej Odpowiedzialności

W świecie programowania obiektowego, zasady SOLID to fundament, na którym budujemy czysty, skalowalny i łatwy w utrzymaniu kod. Dziś przyjrzymy się pierwszej i być może najważniejszej z nich: Regule Jednej Odpowiedzialności (Single Responsibility Principle - SRP). Co tak naprawdę oznacza i dlaczego jej ignorowanie może prowadzić do poważnych problemów w projekcie?

Czym jest Reguła Jednej Odpowiedzialności?

Zasada sformułowana przez Roberta C. Martina (znanego jako "Wujek Bob") jest zwodniczo prosta:

Klasa powinna mieć tylko jeden powód do zmiany.

Co to oznacza w praktyce? Każda klasa w Twojej aplikacji powinna mieć jedną, jasno zdefiniowaną odpowiedzialność. Nie chodzi o to, by klasa miała tylko jedną metodę, ale o to, by wszystkie jej metody i pola były spójne i służyły jednemu, konkretnemu celowi. Jeśli możesz wskazać więcej niż jeden powód, dla którego klasa mogłaby wymagać modyfikacji w przyszłości, to znak, że łamiesz SRP.

Pomyśl o tym jak o szwajcarskim scyzoryku. Jest świetny na biwaku, ale w kuchni do krojenia chleba użyjesz noża do chleba, a do otwierania wina – korkociągu. Każde z tych narzędzi jest wyspecjalizowane i robi jedną rzecz naprawdę dobrze. Tak samo powinno być z Twoimi klasami.

Przykład: Jak złamać zasadę SRP?

Wyobraźmy sobie klasę, która ma zarządzać danymi pracownika. Na pierwszy rzut oka, poniższy kod może wydawać się w porządku.

Przykład złego kodu (naruszenie SRP):

public class Pracownik {
    private String imie;
    private String stanowisko;
    private double pensja;

    public Pracownik(String imie, String stanowisko, double pensja) {
        this.imie = imie;
        this.stanowisko = stanowisko;
        this.pensja = pensja;
    }

    // Odpowiedzialność 1: Logika biznesowa
    public void awansuj(String noweStanowisko, double podwyzka) {
        this.stanowisko = noweStanowisko;
        this.pensja += podwyzka;
        System.out.println(imie + " awansował na stanowisko: " + noweStanowisko);
    }

    // Odpowiedzialność 2: Zapis do bazy danych
    public void zapiszWBazieDanych() {
        // Logika łącząca się z bazą danych i zapisująca obiekt...
        System.out.println("Zapisano pracownika " + this.imie + " w bazie danych.");
    }

    // Odpowiedzialność 3: Generowanie raportu
    public String generujRaportXML() {
        // Logika formatująca dane pracownika do formatu XML...
        return "<pracownik><imie>" + this.imie + "</imie><stanowisko>" + this.stanowisko + "</stanowisko></pracownik>";
    }
}

Ta klasa ma aż trzy różne powody do zmiany:

  1. Zmiana logiki biznesowej: Zmieniają się zasady awansowania pracowników.

  2. Zmiana technologii bazy danych: Przechodzimy z SQL na NoSQL i metoda zapiszWBazieDanych() musi zostać napisana od nowa.

  3. Zmiana formatu raportowania: Dział finansowy prosi o raporty w formacie JSON zamiast XML.

Mieszanie tych trzech różnych odpowiedzialności w jednej klasie to prosta droga do kłopotów.

Konsekwencje braku stosowania SRP

  • Trudności w utrzymaniu (wysokie sprzężenie): Zmiana w jednym obszarze (np. formacie raportu) wymusza modyfikację i ponowne testowanie całej klasy, co zwiększa ryzyko wprowadzenia błędu w zupełnie niepowiązanym module (np. logice biznesowej).

  • Niska czytelność: Klasa staje się "boskim obiektem" (God Object), który robi wszystko. Nowym programistom w zespole trudno będzie zrozumieć jej przeznaczenie.

  • Problemy z testowaniem: Jak napisać prosty test jednostkowy dla logiki awansu, skoro klasa ma zależności związane z bazą danych? Trzeba tworzyć skomplikowane mocki i zaślepki, co utrudnia proces testowania.

  • Ograniczona reużywalność: Chciałbyś ponownie użyć logiki do generowania raportów w innym miejscu aplikacji? Niestety, ciągniesz za sobą cały bagaż związany z danymi pracownika i logiką biznesową.

Jak to naprawić? Refaktoryzacja z SRP

Zgodnie z zasadą jednej odpowiedzialności, powinniśmy rozbić naszą klasę Pracownik na mniejsze, wyspecjalizowane klasy.

1. Klasa Pracownik (tylko dane i logika biznesowa) Ta klasa przechowuje już tylko stan obiektu i metody operujące na tym stanie.

public class Pracownik {
    private String imie;
    private String stanowisko;
    private double pensja;

    public Pracownik(String imie, String stanowisko, double pensja) {
        this.imie = imie;
        this.stanowisko = stanowisko;
        this.pensja = pensja;
    }

    public void awansuj(String noweStanowisko, double podwyzka) {
        this.stanowisko = noweStanowisko;
        this.pensja += podwyzka;
    }

}

Powód do zmiany: Tylko zmiana w logice biznesowej dotyczącej pracownika.

2. Klasa PracownikRepository (odpowiedzialność: persystencja) Ta klasa zajmuje się wyłącznie komunikacją z bazą danych.

public class PracownikRepository {
    public void zapisz(Pracownik pracownik) {
        // Logika zapisu do bazy danych...
        System.out.println("Zapisano pracownika " + pracownik.getImie() + " w bazie danych.");
    }
}

Powód do zmiany: Tylko zmiana technologii lub sposobu zapisu danych.

3. Klasa RaportGenerator (odpowiedzialność: raportowanie) Jej jedynym zadaniem jest tworzenie raportów na podstawie danych pracownika.

public class RaportGenerator {
    public String generujRaportXML(Pracownik pracownik) {
        return "<pracownik><imie>" + pracownik.getImie() + "</imie><stanowisko>" + pracownik.getStanowisko() + "</stanowisko></pracownik>";
    }

    public String generujRaportJSON(Pracownik pracownik) {
        // Logika formatująca do JSON...
        return "{\"pracownik\":{\"imie\":\"" + pracownik.getImie() + "\",\"stanowisko\":\"" + pracownik.getStanowisko() + "\"}}";
    }
}

Powód do zmiany: Tylko zmiana formatu lub zawartości raportu.

Dzięki takiemu podejściu każda klasa ma jedną, klarowną odpowiedzialność. Zmiana wymagań w jednym obszarze dotyka tylko jednej, małej klasy. Kod staje się prostszy do zrozumienia, testowania i ponownego użycia.

Podsumowanie

Reguła Jednej Odpowiedzialności to nie tylko akademicka zasada, ale praktyczne narzędzie, które pomaga pisać lepszy kod. Następnym razem, gdy będziesz tworzyć nową klasę lub modyfikować istniejącą, zadaj sobie pytanie: "Ile powodów do zmiany ma ta klasa?". Jeśli odpowiedź brzmi "więcej niż jeden", wiesz już, co robić. Stosowanie SRP to inwestycja, która zwraca się z nawiązką w postaci czystszego i łatwiejszego w utrzymaniu oprogramowania.

Reguły SOLID

Part 1 of 1