Zasada pojedynczej odpowiedzialności

SRP

Single responsibility principle (tj. tytułowa zasada i w skrócie będę pisać SRP) polega na tym, aby klasa nie miała więcej niż jednego powodu, aby istniała tudzież chodzi też o to, aby klasa była odpowiedzialna tylko za jedną jedyną rzecz.

No ok. Tyle tej prostej teorii. Jeśli chodzi o zastosowanie, to nawet nie tylko chodzi o klasy. Wszystkie interfjesy, metody, funkcje, moduły i pewnie inne struktury powinny spełniać zasadę SRP. Przykładowo klasa Kalkulator powinna spełniać tylko założenia bycia kalkulatorem. Jej metody takie jak dodaj, odejmij itd. powinny robić tylko to znaczą ich nazwy – co też przykłada się na inne zasady, ale o tym będzie kiedy indzie 🙂

To zagadnienie jednak staje się bardziej złożone gdy mamy do czynienia ze złożonymi koncepcjami jak np. Domain-driven design. Wtedy musimy czasami 4 razy przemyśleć co się w tym kodzie dzieje. Jednak może takim kombajnem jak DDD nie będę was dzisiaj zanudzać 😉

Przykład

Założenie

Załóżmy jednak że przed sobą mamy zadanie napisać program, który na podstawie danych, będzie dodawał i odejmował konkretne liczby. Odejmowanie zacznie się od pierwszej liczby ze zbioru sub, a dodawanie sumuje wszystkie liczby ze zbioru add

Przykładowe dane:

{
   "sub" : [ 50, 20, 10 ],
   "add" : [ 30, 10, 20 ]
}
  • sub – liczby do odjęcia ze sobą; tutaj 50 – 20 – 10
  • add – liczby do dodania ze sobą; tutaj 30 + 10 + 20

Analiza

Zatem skoro jest coś do przeliczenia to wtedy potrzebujemy kalkulatora. Kalkulator ten musi umieć tylko dodawać i odejmować wyznaczone liczby. Ok, więc zatem:

  • Klasa Kalkulator – odpowiedzialna tylko za obliczanie
  • metoda dodaj – odpowiedzialna tylko za dodawanie
  • metoda odejmij – odpowiedzialna tylko za odejmowanie

Potrzebujemy jeszcze tylko elementu sterującego programem. Generalnie w takim przypadku sięgam po CQRS, a jeszcze przed nim tworzę warstwę PORT, ale te koncepcje gdybym miał tutaj przestawić, to musiałbym zmienić tytuł artykułu 🙂 W naszym prostym przypadku napiszemy tylko użycie kalkulatora zaraz pod nim

Pseudo-kod kalkulator

Generalnie nie ma co dywagować nad tym jaki język wybrać. Ja oczywiście użyję konkretnego języka, ale postaram się, aby ten przykład był na tyle uniwersalny, żeby dało się go przerobić na dowolny język programowania:

Klasa Calculator:

class Calculator
{
  public add(numbers : number[]) : number
  {
    let result : number = 0;
    numbers.forEach((number) => {
      result += number;
    });
    return result;
  }

  public sub(numbers : number[]) : number
  {
    let result : number = numbers[0] * 2;
    numbers.forEach((number) => {
      result -= number;
    });
    return result;
  }
}

const data = {
   "sub" : [ 50, 20, 10 ],
   "add" : [ 30, 10, 20 ]
};

const calc = new Calculator;
const addedNumbers = calc.add(data["add"]);
const subbedNumbers = calc.sub(data["sub"]);

console.log(addedNumbers, subbedNumbers);

i wszystko wygląda na ułożone i mające jakiś sens. Pomimo że ten kod dałoby radę bardziej uprościć czy ukrócić, np. tylko raz zadeklarować forEach, to póki co nie ma co tego robić.

Nowe założenia

Czas na kwintesencję zasady SRP – chodzi o to, że jeśli założenia mają coś dołożyć to dzięki osobnej odpowiedzialności elementów kodu możemy łatwo to wdrożyć.

Nasz powyższy kod działa dobrze, ale tylko do czasu. Gdy ktoś poda pustą tablicę w sub to mamy na wyniku wartość NaN czyli Not a Number i to nam się zgadza – nic przecież liczbą nie jest 😉

Ok, zatem musimy zmodyfikować kod tak, aby nastąpiła walidacja danych. Dużo początkujących programistów w zrobiłoby to w samych metodach do dodawania i odejmowania. To jest częsty błąd, bo nie raz przyjdzie nam walidować (czyli sprawdzać) dane w taki sam sposób. Co zrobić? No a walidator musi być jako osobna klasa.

Naszym nowym założeniem jest to, aby sprawdzić dane dla sub – jeśli są prawidłowe wtedy możemy przystąpić do odejmowania. Dla ułatwienia sprawdzajmy tylko czy ilość danych jest większa od 0 (czyli czy mamy przynajmniej jedną liczbę)

Pseudo-kod walidator

Nasz nowy kod będzie wyglądać tak:

class Calculator
{
  public add(numbers : number[]) : number
  {
    let sum : number = 0;
    numbers.forEach((number) => {
      sum += number;
    });
    return sum;
  }

  public sub(numbers : number[]) : number
  {
    let sum : number = numbers[0] * 2;
    numbers.forEach((number) => {
      sum -= number;
    });
    return sum;
  }
}

class NumberListValidator
{
  public issetNumbers(numbers : number[]) : boolean
  {
    return numbers.length > 0;
  }
}

let data = {
   "sub" : [ /* 50, 20, 10 */ ],
   "add" : [ 30, 10, 20 ]
};

const calc = new Calculator;
const numbersValid = new NumberListValidator;

const addedNumbers = calc.add(data["add"]);
console.log(`Added numbers = ${addedNumbers}`);

if ( numbersValid.issetNumbers(data["sub"]) ) {
    const subbedNumbers = calc.sub(data["sub"]);
    console.log(`Subtracted numbers = ${subbedNumbers}`);
} else {
    throw new Error("data.sub is empty");
}

Zauważcie że w kodzie zakomentowałem dane z sub – zrobiłem to po to, aby przetestować walidację. Też się popatrzcie na metodę walidatora – nie jest dedykowana tylko dla sub. Ona tylko i wyłącznie mówi o tym, że sprawdza czy są zawarte jakieś liczby. To jest jej odpowiedzialność i tym samym zyskuje na uniwersalności. Jak to się mówi w prostocie siła! – więc jeśli przyjdzie nam kiedyś sprawdzić czy ilość zawartych w tablicy liczb jest większa niż zero to użyjemy naszego walidatora.

Oprócz tego, jeśli by się nam zdarzyło poprawić dodawanie, odejmowanie albo walidację to robimy to tylko to w tych klasach. Nie musimy wtedy przeszukiwać wszystkich plików kodu i sprawdzać gdzie tam było jakie sprawdzenie albo jakie dodawanie.

Podsumowanie

Dzięki temu SRP zyskujemy taki bardziej czysty kod – lepszy do odczytu i łatwy do rozszerzania. Dla tych bardziej ciekawskich to zasada pojedynczej odpowiedzialności jest częścią bardziej złożonej koncepcji takiej jak SOLID, w której każda literka oznacza inną zasadę, a dzisiaj przedstawiłem tylko literkę S. Dodatkowo dzięki SRP po części możemy lepiej wdrażać inne zasady, takie jak KISS – czyli bez udziwnień w kodzie czy jak DRYnie powtarzaj się, którymi SOLID fajnie jest rozszerzać i które bardzo polecam i pewnie które kiedyś opiszę 😅

Język programowania jaki użyłem do przykładów to TypeScript – taki dla mnie kompromis pomiędzy JavaScript, a C# lub C++. A sam kod realizuje dodawanie i odejmowanie, które jest łatwo samemu wymyślić pisząc własny program – więc zalecam popróbować nauczyć się tej zasady na takich błahostkach – jeśli ogarniecie to w małych rzeczach to w większych też dacie radę.

Dziękuję ciekawskim programistom – chcę aby moje artykuły pomogły wam się stawać światowej klasy specjalistami! 😀 Dla tych co nie kodują, a dotrwali do tego momentu – WOW Ale tak serio! To się nazywa wytrwałość! 😉

Do następnego – powodzenia! 😀

Subscribe
Powiadom o
guest
2 komentarzy
najstarszy
najnowszy oceniany
Inline Feedbacks
View all comments
trackback
2 lat temu

[…] jest dużo i ich nauka też trochę schodzi czasu. Też na moim blogu znajdziecie opisy KISS oraz SRP. Chciałbym tak na prawdę opisać wszystkie jakie znam, ale.. kiedy ja na to czas znajdę? 😱 No […]

trackback
2 lat temu

[…] miejsce ma rzecz z dziedziny programowania, a dokładniej SRP z SOLID. To dla mnie miłe zaskoczenie, bo widzę ilu programistów chce się dowiedzieć czegoś nowego […]

Włączyć powiadomienia? Bardzo pragnę Dam sobie radę