PHP: SOLID [S] Zasada pojedynczej odpowiedzialności
W oryginale „Single Responsibility Principle„.
Wstęp
Zasada z pozoru prosta, natomiast stosowanie jej w praktyce wymaga przelania odrobiny potu.
Autor zasady pojedynczej odpowiedzialności twierdzi, że jedna klasa powinna mieć jeden powód do zmiany, powinna zatem robić jedną konkretną rzecz.
Kto z nas nie pisał na początku swej przygody z programowaniem klas typu „scyzoryk szwajcarski„, oczywiście taki kod jest niezgodny z pierwszą zasadą SOLID.
Praktyka
Aby klasy miały jedną odpowiedzialność należy pochylić się na początku i poświęcić sporo czasu na odpowiednim zaprojektowaniu aplikacji. Proponuję na początku spisać wszystkie klasy jakie będą nam potrzebne i zastanowić się nad ich relacją.
Każda klasa powinna ściśle być odpowiedzialna za jedną rzecz, najlepiej gdyby każdą klasę udało nam się opisać maksymalnie w 25 słowach, jeśli w opisie naszej klasy znajduje się zbyt wiele spójników „i”, „lub”, powinniśmy rozważyć przeniesienie części odpowiedzialności do oddzielnych klas.
Poniżej przedstawię ilustrację pewnej klasy typu „scyzoryk szwajcarski„:
class Basket { private $allProducts = []; public function __construct(array $data) { if (isset($data['name']) && isset($data['price'])) { $this->allProducts[] = ['name' => $data['name'], 'price' => $data['price'], 'bruttoPrice' => (23 / 100) * $data['price']]; } } public function getAllProducts() : array { return $allProducts; } private function erase() { $this->allProduct = []; } }
Powyższa klasa przechowuje listę produktów, oblicza cenę brutto, posiada metody odpowiedzialne za czyszczenie koszyka zakupowego.
Możemy do niej dodać kolejne metody związane z koszykiem, bowiem jest to przecież klasa o nazwie „Basket„.
Możemy również dodać kolejne indeksy w tablicy produktów jak na przykład „Opis produktu„. A co możliwością wyświetlania cen po zastosowaniu kodu rabatowego ? Również dodamy tutaj.
Proszę zobaczyć, w jaki sposób jest obliczana kwota brutto, takie podejście będzie z pewnością wymagało powielenia kodu. Prawidłowe podejście wymaga, aby operacje obliczenia ceny odbywały się w osobnym do tego przeznaczonym miejscu.
Przedstawię poniżej bardzo prosty przykład rozdzielenia powyższych funkcjonalności.
trait PriceUtilities { private $tax = 23; public function getBruttoPrice(float $price) : float { return ($this->tax / 100) * $price; } } class EntityProduct { use PriceUtilities; private $name = null; private $price = null; public function __construct (string $name = null, float $price = null) { $this->name = $name; $this->surname = $price; } } class Basket { private $products = []; public function erase() { $this->products = []; } public function getAll() : array { return $this->products; } }
Jak wspomniałem wcześniej, przykład ilustruje pewien problem który możemy napotkać w sytuacji, gdy pewna klasa zaczyna się niebezpiecznie rozrastać. Najlepiej w takiej sytuacji zapobiec ewentualnym błędom i rozdzielić funkcjonalności.
Powyżej obsługę koszyka zawarliśmy w klasie „Basket„.
Reprezentację pojedynczego rekordu przenieśliśmy do osobnej klasy „EntityProduct„.
Obliczanie ceny brutto w postaci trait umieściliśmy również osobno, dzięki temu kod zawarty wewnątrz nie będzie powielany. Można się pokusić o zastąpienie trait klasą, jeśli zamierzamy wprowadzić różnego rodzaju promocje, czyli kolejne warunki.
Podsumowanie
Zasady SOLID zostały wymyślone, aby ułatwić życie programistom a nie utrudniać. Nie ma sensu stosowanie ich na siłę.
Oczywiście powinniśmy się do nich stosować, ale każdy problem rozpatrywać indywidualnie, czasem umieszczanie jednej linii kodu w osobnej klasie nie jest dobrym pomysłem.