Wróć do strony głównej
Angular

Angular & Single Responsibility Principle

Wstęp

SOLID to zbiór zasad, dzięki którym możemy tworzyć kod, który jest skalowalny, da się łatwo rozszerzać, oraz zmieniać implementacje istniejących mechanizmów. Mimo, że większość zasad pochodzi z ubiegłego wieku, nadal są aktualne i są jednymi z najważniejszych elementów programowania obiektowego. Warto zdawać sobie z nich sprawę w codziennej pracy Angular developera.

 

Na skrót SOLID składają się:

  • Single Responsibility Principle,
  • Open/Closed Principle,
  • Liskov Substitution Principle,
  • Interface Segregation Principle,
  • Dependency Inversion Principle.

Single Responsibility Principle

Na pierwszy ogień idzie literka S. Jej rozwinięcie to Single Responsibility Principle, co w tłumaczeniu na język polski brzmi: „zasada pojedynczej odpowiedzialności”.

Credit: Derick Bailey, CC-SA 3.0

Tak jak widzimy tutaj na obrazku – możemy mieć kod (klasę/metodę) jak scyzoryk – który będzie robił wszystko. Ale to nie oznacza, że powinniśmy tak robić.

 

“A class should have one, and only one, reason to change”.

 

Powtarzając za Robertem Martinem – tworząc nasz kod, powinniśmy pisać go w taki sposób, aby w przypadku zmiany wymagań, zmieniać daną klasę z jednego powodu.

Można to też opisać zdaniem: klasa/obiekt powinien mieć jedną odpowiedzialność.

 

Co daje nam ta reguła?

  • kod jest łatwiejszy do pisania (bo pokrywamy mniej funkcjonalności w ramach jednego pliku niż jeden plik z wieloma),
  • zapobiega nieoczekiwanym „side-effectom”,
  • redukuje liczbę zmienianych klas (im więcej odpowiedzialności ma klasa, tym częściej będzie zmieniana). Dążymy do robienia jak najmniejszych zmian (bo każda zmiana pociąga za sobą zmianę klas/komponentów zależnych od zmienianego obiektu).

 

Klasy/obiekty, które mają jedną odpowiedzialność są:

  • łatwiejsze do zrozumienia,
  • prowadzą do mniejszej liczby bugów,
  • przyspieszają proces developmentu.

 

Jak najlepiej trzymać się tej zasady? Przed każdą zmianą kodu (lub gdy siadamy do pisania nowego), wystarczy zadać sobie pytanie: jaką odpowiedzialność ma ten kod? Jeśli zawiera się tam więcej niż jedno „i”, to najprawdopodobniej nadaje się to do refaktoru.

 

Spójrzmy na poniższy przykład. 

Powyższy wycinek jest fragmentem kodu komponentu, który składa się na drzewo folderów. Widzimy więc, że ma on takie metody jak:

  • calculateIndent → która służy do wyliczenia wcięcia, jakie musi mieć komponent w drzewie w zależności od jego zagnieżdżenia,
  • expand → która służy do otworzenia folderu i pokazania jego dzieci,
  • getColorByStatus → która służy do wyliczenia koloru, jaki powinien mieć folder (w zależności od jego statusu – czy jest zarchiwizowany, czy nie).

 

Jak widać, dzieje się tutaj zbyt wiele. Przede wszystkim, komponent zamiast mieć wiedzę, kogo zapytać o poszczególne dane, sam wszystko wylicza. Jego kod jest trudny do zrozumienia (ponieważ zawiera implementacje wielu różnych metod) jak i pokrycia testami jednostkowymi.

 

Można to rozwiązać, wydzielając te metody do odpowiednich serwisów. 

 

 

Mielibyśmy wtedy takie serwisy:

  • FolderIndentService – który przejmie implementację metody calculateIndent – będzie więc wyliczał wcięcie danego folderu w zależności od jego zagnieżdżenia w drzewie,
  • FolderToggleService – który przejmie implementację metody expand – będzie więc zajmował się otwieraniem/zamykaniem folderu,
  • FolderColorService – który przejmie implementację metody getColorByStatus – będzie więc wyliczał kolor folderu w zależności od jego statusu.

 

Przy takim podziale komponent będzie tylko wiedział, do kogo zwrócić się o wykonanie akcji, obliczenie danej wartości. Dzięki temu łatwiej będzie napisać testy jednostkowe do poszczególnych funkcjonalności. W ten sposób będzie można reużyć tych serwisów (i ich metod) w innych miejscach, co przełoży się na zredukowanie ewentualnych zduplikowanych fragmentów kodu.

 

Spójrzmy na kolejny przykład.

 

Załóżmy, że mamy dialog do tworzenia folderu. Zawiera on formularz, oraz przycisk do zatwierdzenia go. Oprócz tego wyświetla też błędy walidacji (np. pole „name” jest obowiązkowe). Wyobraźmy sobie teraz taką sytuację, że w tym komponencie zaimplementowaliśmy teraz edycję folderu. Czyli w jednym komponencie obsługujemy:

  • tworzenie folderu,
  • edycję folderu,
  • warstwę formularza, walidacji danych.

 

Jak widać, również tutaj zbyt dużo się dzieje. Jako rozwiązanie moglibyśmy wydzielić 3 komponenty, jak na obrazku poniżej:

 

 

  1. FolderFormComponent – który zawierałby tylko i wyłącznie formularz i zajmował się jego walidacją.
  2. FolderEditDialog – który opakowywałby FolderFormComponent i zawierał w sobie logikę edycji folderu (np. wysłanie request’a na backend).
  3. FolderCreateDialog – który opakowywałby FolderFormComponent, ale zawierał w sobie tylko logikę tworzenia folderu.

 

Mielibyśmy więc rozdzielone:

  • tworzenie,
  • edycję,
  • prezentację, walidację danych.

 

W ten sposób podążamy regułą Single Responsibility Principle.

 

W następnej części zajmiemy się Open/Closed Principle. Stay tuned 🙂

O autorze

Wojciech Janaszek

Jestem Angular i NestJS developerem. Swoją przygodę zaczynałem od pierwszych wersji „nowego” Angulara (2 wzwyż). Od niedawna korzystam również z NestJS (którego nauka znając dobrze Angulara przychodzi łatwo – wszystko jest bardzo podobne). W swojej pracy dużą uwagę przykładam do tzw. clean code i clean architecture. Lubię mieć “porządek” w kodzie 🙂 W wolnym czasie interesuję się sportowymi samochodami, ogólnie pojętym motorsportem. Gram również amatorsko w siatkówkę.

Zapisz się do naszego newslettera. Bądź na bieżąco z najnowszymi trendami, poradami, meetupami i stań się częścią społeczności Angulara w Polsce. Rynek pracy docenia członków społeczności.

3 komentarzy

  1. AngularUser

    To ja mam pytanie. Czy sa sytuacje gdzie mozna przesadzic z ta zasada ? Tzn, czy to nie sprawi, ze bedziemy mieli cale mnostwo komponentów po których ciężko będzie nawigować ? Powiem tak – Pracuje w dosc specyficznej firmie gdzie nie pisze sie unit testow itp. i ktoś w jednym projekcie probowal zastosowac wlasnie ta zasade, robiac pojedyncze komponenty np. na jeden input ? Czy cos takiego zawsze ma sens ?

    • Wojciech Janaszek

      Jak we wszystkim, należy znać umiar, znaleźć tzw. złoty środek. Żadne skrajności nie są dobre 😉

      Tworzenie komponentu na input wydaje się overkillem, bo tak właściwie wystarczyłoby pewnie stworzyć klasę css żeby wystylowac taki input, i potem nadawać ją według potrzeby (podejście w stylu tailwinda). Chyba że rzeczywiście taki input miałby mieć jakąś dodatkową logikę, to wtedy lepiej pasowałoby jak dla mnie mieć stworzyć dyrektywę.

      Ogólnie warto sobie patrzeć jak napisany jest Angular Material – tam nie ma zbyt wiele komponentów prezentacyjnych, jest za to dużo małych smart komponentów, które są rozszerzane poprzez content projection i ew. nakładanie dyrektyw.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *