SOLID 5

Zasada jednej odpowiedzialności

Zasada odwrócenia zależności – piąta zasada SOLID.

Nawiązując do myśli Roberta C. Martin’a – “Jedną z różnic pomiędzy bystrymi programistami a profesjonalistami jest to, że profesjonaliści rozumieją, że czytelność jest wszystkim. Profesjonaliści piszą kod zrozumiały dla innych.” stwierdzam, że główną zasadą programowania jest zasada czystego i czytelnego kodu. W programowaniu obiektowym podstawowym założeniem czystego kodu jest wymyślone przez Martin’a, 5 zasad dobrego programowania tzw. SOLID. Ostatnią z pięciu założeń SOLID, jest zasada odwrócenia zależności. Mówi o tym, że: wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych – zależności między nimi powinny wynikać z abstrakcji. Czyli innym słowy w klasach i metodach nie powinniśmy używać żadnych nazw konkretnych klas, mogą występować nazwy tylko interfejsów i klas abstrakcyjnych. Również klasy nie powinny dziedziczyć po konkretnych klasach, jedynie po klasach abstrakcyjnych i interfejsach. W tym artykule opisze tą zasadę oraz przedstawię przykład kodu programu przed użyciem tej zasady i po.

Zastosowanie

Zasada odwrócenia zależności z ang. Dependency inversion principle jak już wyżej wspomniałem mówi o tym, że wysokopoziomowe klasy nie powinny zależeć od klas niskopoziomowych. W myśl tej zasady wszystkie zależności powinny w jak największym stopniu zależeć od abstrakcji, a nie od konkretnego typu. Moduły (klasy) wysokiego poziomu najczęściej są odpowiedzialne za ważne decyzje strategiczne i modele danej aplikacji. Dlatego największym stopniu odpowiadają  one za funkcjonowanie całej aplikacji. Gdyby moduły wysokiego poziomu zależały od modułów niskiego poziomu, to zmiany elementów niskiego poziomu wymuszałaby również wprowadzenie zmian poziomów wyższych. Dodatkowo w momencie, gdy moduły wysokopoziomowe zależą od niskopoziomowych, ponowne ich wykorzystanie często jest utrudnione. Odwrócenie tej zależności w drugą stronę powoduje, że będzie można je wykorzystać wielokrotnie. Zatem kod źródłowy nie powinien być zależny od konkretnej klasy, zależności takie powinny kończyć się na klasach abstrakcyjnych lub interfejsach.

Przykładek klasy wysokiego poziomu jest warstwa przetwarzania, prezentowania danych, natomiast warstwą niskiego poziomu jest warstwa dostępu do danych. Moduł przetwarzania danych jest zależny od modułu dostępu do danych. A według tej zasady nie może istnieć mocne powiązanie miedzy tym modułami. Nie można użyć silnych połączeń miedzy nimi. Dlatego zasada odwrócenia zależności odwraca tą logikę,  niskopoziomowe moduły podpinamy do interfejsów określonych przez moduł wysokiego poziomu, gdzie celem jest likwidacja silnych sprzężeń.

Przykład

W programie poniżej mamy dwie klasy SMS, która jest klasą niskopoziomową oraz klasę NotificationManager – klasa wysokopoziomowa.

  1. <?php  
  2.   
  3. class User  
  4. {  
  5.   
  6. }  
  7.   
  8. class SMS  
  9. {  
  10.     public function send()  
  11.     {  
  12.         echo 'Wysylam powiadomienie SMS';  
  13.     }  
  14. }  
  15.   
  16. class NotificationManager  
  17. {  
  18.     public function sendNotification(User $user, $message)  
  19.     {  
  20.         $sms = new SMS();  
  21.         $sms->send($user, $message);  
  22.     }  
  23. }  
  24.   
  25. $nm = new NotificationManager();  
  26. $nm->sendNotification(new User(), 'witam');  
  27.   
  28. ?>

Kod 1. Kod bez zastosowania piątej zasady SOLID

W klasie NotificationManager na sztywno stworzono obiekt klasy SMS, czyli na sztywno wiążemy metodę przesyłania powiadomień z menadżerem powiadomień, co wiąże się z  występowaniem silnego sprzężenia. Żeby program był zgodny z metodą odwrócenia zależności należy zrobić tak, żeby odwrócić tą zależność i zniwelować silne sprzężenie. Zrobimy tak, żeby moduł wysokopoziomowy był jak najbardziej elastyczny, żeby nie był na sztywno powiązany z jedną określoną klasą tylko akceptował konkretny interfejs.

  1. <?php  
  2.   
  3. class User  
  4. {  
  5.   
  6. }  
  7.   
  8. interface NotificationService  
  9. {  
  10.     public function send(User $user, $message);  
  11. }  
  12.   
  13. class SMS implements NotificationService  
  14. {  
  15.     public function send(User $user, $message)  
  16.     {  
  17.         echo 'Wysylam powiadomienie SMS';  
  18.     }  
  19. }  
  20.   
  21. class NotificationManager  
  22. {  
  23.     public function sendNotification(User $user, NotificationService $notificationservice, $message)  
  24.     {  
  25.         $notificationservice->send($user, $message);  
  26.     }  
  27. }  
  28.   
  29. $nm = new NotificationManager();  
  30. $nm->sendNotification(new User(), new SMS() ,'witam');  
  31.   
  32. ?>

Kod 2. Kod z zastosowaniem piątej zasady SOLID

Jak można zauważyć w programie powyżej stworzono interfejs NotificationService określający, że każda klasa implementująca ten interfejs musi posiadać metodę send. Klasa SMS będzie natomiast implementowała ten interfejs. Metoda sendNotification modułu wysokopoziomowego, określa że będzie akceptować tylko takie obiekty , które zgodne są z tym interfejsem NotificationService. Teraz widać, że wdrożono zasadę odwrócenia zależności. Metoda sendNotification i moduł wysokiego poziomu NotificationManager nie jest na sztywno uzależniony od obiektu konkretnej klasy powiadomień, tylko definiuje jakie interfejsy będzie akceptował i te moduły niskopoziomowe muszą po prostu te interfejsy implementować, tak żeby mogły współpracować z menadżerem powiadomień.

Podsumowanie

Podsumowując, piąta zasada SOLID czyli zasada odwrócenia zależności przynosi wiele korzyści, miedzy innymi system jest bardziej elastyczny, a kod pisany w myśl tej zasady jest dużo prostszy w użyciu, można go łatwiej przenieść na inne projekty. Przykładem tak napisanego kodu jest kod w aplikacja Personal Budget. Zasada ta daje również większą pewność, że zmiana komponentu nie przyniesie nam niepożądanych skutków w programie.