Zasady SOLID to zestaw dobrych praktyk w programowaniu obiektowym. Ich syntezy dokonał amerykański programista Robert Martin w książce pt. „Agile. Programowanie zwinne: zasady, wzorce i praktyki zwinnego wytwarzania oprogramowania w C#”. Stosowanie metodyki SOLID jest wymagane od profesjonalnych programistów.

W życiu zazwyczaj nie ma rozwiązań idealnych, które zadowoliłyby każdego. Tak samo jest z zasadami SOLID. W większych projektach nie wszystkie metody udaje się w pełni spełnić. Jednak, tak jak w życiu, należy dążyć do ideału i starać się napisać jak najlepszy kod: prosty, funkcjonalny, przyjazny dla rozszerzeń i eksploatacji.

SOLID to akronim od angielskich wyrażeń:


S jak zasada pojedynczej odpowiedzialności.

Klasa powinna posiadać tylko pojedynczą odpowiedzialność. Nie powinien istnieć więcej niż jeden powód, abyśmy zamierzali zmodyfikować klasę.

Jeżeli chcielibyśmy zmienić jedną funkcjonalność, która jest zawarta w skomplikowanym pakiecie funkcji, możemy wpłynąć na inne funkcjonalności w sposób niezamierzony. Klasa posiadająca więcej odpowiedzialności utrudnia utrzymanie porządku w kodzie i jego rozwój. Struktura programu nie stosującego pierwszej z zasad SOLID, po dłuższym czasie będzie trudna do zrozumienia nawet dla jej autora.

Przed
Po

O jak zasada otwarte-zamknięte.

Składniki aplikacji powinny być otwarte na rozszerzanie, a zamknięte na modyfikacje. Dzięki temu kod będzie mógł zostać wykorzystany w wielu różnych, ale podobnych zastosowaniach.

Przykładowo stworzenie składnika aplikacji pierwszego sklepu internetowego zgodnie z zasadą Open/Closed przyniesie w przyszłości korzyść w postaci powielania kodu i jego prostego dostosowania do szczegółowych warunków innych sklepów. Tworzenie od podstaw każdej witryny to niepotrzebne powtarzanie wielu czynności.

Przykład, w którym nie można dodać nowej klasy bez modyfikacji już istniejącej:

Przed

Poniżej dodanie nowej figury nie wymaga zmian w istniejących klasach:

Po

L jak zasada podstawienia Liskov.

Zasada ta mówi, że dla klas pochodnych musi być zachowana pełna zgodność wszystkich metod i interfejsów z klasą bazową. Dzięki temu w miejsce klasy bazowej można użyć dowolnej klasy dziedziczącej.

Podstawienie Liskov nakazuje przemyślenie mechanizmu dziedziczenia w aplikacji. Zabronione jest pozostawienie implementacji jakiejś metody pustej lub umieszczenie tam wyjątku. Ponadto klasy pochodne nie powinny nadpisywać metod klas bazowych. Mogą je jedynie rozszerzać. Dowolny kod działający dla klasy bazowej powinien działać dla wszystkich klas dziedziczących.

Często, aby spełnić wymagania podstawień Liskov należy zastosować polimorfizm.

Dziedziczenie kwadratu z prostokąta nie zachowuje zasady podstawienia Liskov, ponieważ nadpisano metody:

Przed

Przykład poprawiony:

Po

I jak zasada segregacji interfejsów.

Interfejsy powinny być jak najmniejsze, tak aby były implementowane w całości. Według tej zasady klasy nie powinny być zależne od metod, których nie używają.

Segregacja dla interfejsów wygląda podobnie jak pojedyncza odpowiedzialność dla klas. Wiele wyspecjalizowanych interfejsów zastępuje jeden zbiorowy. Tylko tak można uniknąć dołączania do klas implementacji funkcji, których nie można zrealizować.

Interfejsy dziedziczą wszystkie metody, również niepotrzebne:

Przed(1)

Interfejsy nie posiadają zbędnych metod, ale powielają zdefiniowane w innych interfejsach:

Przed(2)

Posegregowane interfejsy:

Po

D jak zasada odwracania zależności.

Wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych – zależności między nimi powinny wynikać z abstrakcji.

Oznacza to, że w klasach lub metodach nie można odwoływać się do innych konkretnych klas, a jedynie do klas abstrakcyjnych i interfejsów. To jest zależność od abstrakcji. Dzięki temu instancja nie będzie ograniczona typem wprowadzanych danych – konkretną klasą, bo każda będzie zobowiązana przez interfejs do zdefiniowania jego wszystkich metod.

Moduł wysokopoziomowy ResultManager tworzy instancję modułu niskopoziomowego TxtFile i staje się od niej zależny:

Przed

Po odwróceniu zależności moduł wysokopoziomowy ResultManager jest zależny od abstrakcyjnego interfejsu :

Po