Zasady SOLID w praktyce. Kiedy abstrakcja naprawdę pomaga?

Bruno Krawczyk • 2 listopada 2026
Mężczyzna w okularach pracuje przy laptopie, otoczony kodem CSS. To symbol solidnego programowania i tworzenia stron internetowych.

Spis treści

Dodanie drugiego operatora płatności miało zająć godzinę, a kończy się poprawkami w kontrolerze, bazie i testach. Zasady SOLID zwykle ograniczają taki efekt, choć nie każda klasa potrzebuje własnego interfejsu i osobnej warstwy.

Diagram klas przedstawiający interfejs ISwitch, implementowany przez LEDSwitch, który steruje LEDLight. To przykład solidnego programowania.

Projektuj pod zmiany, które naprawdę mogą nadejść

  • SRP pomaga ustalić, kto powinien odpowiadać za daną zmianę.
  • OCP i LSP chronią rozszerzenia przed naruszaniem działającego kodu.
  • ISP i DIP ograniczają zależności do potrzebnych kontraktów.
  • Prosta reguła: dodaj abstrakcję, gdy izoluje zmienny szczegół albo ułatwia test, a nie dla samej elegancji.

Pięć liter opisuje decyzje projektowe, nie checklistę

Gdy jedna klasa rozlicza płatność, zapisuje zamówienie i wysyła e-mail, każdy z tych obszarów może wymusić jej zmianę. SOLID porządkuje zależności między takimi elementami, ale nie mówi, że każda metoda musi trafić do osobnej klasy.

Robert C. Martin przedstawił zasady projektowania obiektowego w pracy Design Principles and Design Patterns. Pięć nazw pomaga nazwać konkretne problemy w kodzie:

SkrótPełna nazwaPytanie, które warto zadać
SSingle Responsibility PrincipleCzy jedna grupa interesariuszy może wymusić kilka niezależnych zmian w tej klasie?
OOpen/Closed PrincipleCzy nowe zachowanie można dodać bez ryzykownego ruszania stabilnej logiki?
LLiskov Substitution PrincipleCzy implementacja zastępcza zachowuje obietnice kontraktu?
IInterface Segregation PrincipleCzy klient zależy tylko od operacji, których rzeczywiście używa?
DDependency Inversion PrincipleCzy logika biznesowa zna szczegóły techniczne, których nie musi znać?

W nazwach łatwo utknąć, a sens widać dopiero przy zmianie. Uważam, że najlepszym testem SOLID jest pytanie, ile niepowiązanych miejsc trzeba poprawić po zmianie wymagania.

Nowy operator płatności nie powinien przepisywać całego zamówienia

Załóżmy, że aplikacja obsługuje jednego operatora, a teraz ma przyjąć drugiego. Jeśli kod aplikacyjny rozpoznaje nazwy dostawców w wielu miejscach, nowa integracja rozleje się po całym systemie.

Handler zamówienia może koordynować proces, ale szczegóły płatności powinien przekazać kontraktowi, na przykład IPaymentGateway. Implementacje dla poszczególnych operatorów realizują ten sam kontrakt, a rejestracja zależności wybiera właściwą. DIP odwraca kierunek zależności: logika aplikacji zna abstrakcję, a adapter infrastruktury ją implementuje.

W tym układzie pięć zasad wspiera jedną zmianę na różne sposoby. Osobny serwis płatności ogranicza odpowiedzialność handlera (SRP), nową bramkę można dodać jako implementację (OCP), każda bramka powinna dotrzymywać tego samego kontraktu (LSP), wąski interfejs nie zmusza klienta do zależności od nieużywanych metod (ISP), a handler nie tworzy konkretnego klienta HTTP (DIP).

Kontrakt musi opisywać także zachowanie, nie samą sygnaturę metody. Jeśli jeden operator zwraca „zaakceptowano” dopiero po potwierdzeniu, a drugi oznacza tym statusem jedynie przyjęcie żądania, implementacje nie są wymienne, nawet gdy obie zwracają ten sam typ PaymentResult.

Interfejs pomaga, gdy izoluje zmianę albo upraszcza test

Każda abstrakcja ma koszt: dodatkowy typ, konfigurację i miejsce, w którym trzeba szukać przepływu. Wprowadzam ją wtedy, gdy oddziela zmienny szczegół od stabilnej logiki albo pozwala sprawdzić tę logikę bez uruchamiania infrastruktury.

SytuacjaPrawdopodobna przyczyna lub skutekCo zrobić
Wiele miejsc tworzy klienta konkretnego operatoraLogika aplikacji zależy od infrastrukturyWydziel kontrakt i adapter, a tworzenie klienta zostaw konfiguracji zależności
Klasa implementuje metody, których nie może obsłużyćInterfejs łączy zbyt wiele rólPodziel go według potrzeb klientów, na przykład osobno na odczyt i zapis
Nowa klasa pochodna zmienia sens wyniku lub zgłasza wyjątek przy poprawnym wywołaniuImplementacja łamie oczekiwania kontraktuPopraw kontrakt albo wybierz kompozycję zamiast dziedziczenia
Prosty endpoint dostaje kilka interfejsów i klas pośrednichAbstrakcje wyprzedziły rzeczywistą potrzebęZostaw prostą implementację; wydziel granicę dopiero przy zmianie lub trudnym teście

Przy jednym operatorze i braku testów wymagających podmiany bramki bezpośrednia zależność może być rozsądnym początkiem. Gdy pojawia się druga implementacja albo test procesu wymaga odcięcia sieci, granica zaczyna zwracać koszt swojego utrzymania.

Podobnie jest z OCP: nie trzeba przewidywać każdej przyszłej odmiany rabatu. Jeśli reguły rabatowe regularnie się zmieniają, osobne implementacje strategii pozwalają dodać nową regułę bez rozbudowy długiego łańcucha warunków. Jeśli reguła jest stała i krótka, dodatkowa hierarchia typów może tylko utrudnić czytanie.

Badanie kodu uczenia maszynowego wykazało poprawę rozumienia

Obawa przed nadmiarem warstw ma sens, ale równie słabe jest założenie, że zasady projektowe niczego nie wnoszą. W 2024 roku Raphael Cabral i współautorzy opisali w pracy Investigating the Impact of SOLID Design Principles on Machine Learning Code Understanding kontrolowany eksperyment z udziałem 100 analityków danych w trzech niezależnych próbach.

Uczestnicy analizowali rzeczywisty kod przemysłowego projektu ML w wersji pierwotnej albo przebudowanej z uwzględnieniem SOLID. Autorzy podali statystycznie istotne dowody na poprawę rozumienia kodu w grupie pracującej z wersją zgodną z tymi zasadami, ale streszczenie badania nie podaje wielkości efektu. Wyniku nie należy więc czytać jako obietnicy, że każdy refaktoring SOLID przyspieszy pracę; pokazuje on raczej, że porządek w granicach kodu może pomóc także w projektach ML.

To dobry argument za czytelną strukturą, nie za mechanicznym rozbijaniem klas. W eksperymencie porównano konkretne wersje kodu, a nie każdą możliwą interpretację SOLID.

Jeśli liczba warstw rośnie szybciej niż liczba powodów do zmian

W dyskusjach programistów wraca zastrzeżenie, że SOLID bywa używane jako uzasadnienie nadmiernego rozdrobnienia kodu. Zgadzam się z tym zarzutem wtedy, gdy zespół tworzy interfejsy bez drugiej implementacji, testu wymagającego izolacji czy odrębnej odpowiedzialności.

Przy przeglądzie zmiany stosuję prostą regułę: jeśli nowa klasa oddziela powód do zmiany lub pozwala sprawdzić istotne zachowanie bez zależności zewnętrznej, zostaw ją. Jeśli tylko przenosi wywołanie o jeden poziom niżej i nie upraszcza ani zmiany, ani testu, usuń ją lub poczekaj na rzeczywistą potrzebę.

Wróćmy do operatora płatności. Celem nie jest uzyskanie maksymalnej liczby interfejsów, lecz doprowadzenie do sytuacji, w której nowa integracja nie wymaga poprawiania logiki zamówienia, a testy mogą sprawdzić jej zachowanie bez prawdziwego połączenia z dostawcą. Tak rozumiane solidne programowanie mierzy jakość granic po koszcie następnej zmiany.

FAQ - Najczęstsze pytania

Handler zamówienia może przekazywać szczegóły płatności przez kontrakt, taki jak IPaymentGateway. Każdy operator implementuje ten sam kontrakt, a konfiguracja zależności wybiera właściwą implementację. Dzięki temu handler nie musi tworzyć konkretnego klienta HTTP ani rozpoznawać dostawców w wielu miejscach.

Interfejs jest przydatny, gdy oddziela zmienny szczegół od stabilnej logiki albo pozwala testować proces bez prawdziwego połączenia z dostawcą. Przy jednym operatorze i bez testów wymagających podmiany bramki bezpośrednia zależność może być rozsądnym początkiem. Granica zaczyna się opłacać, gdy pojawia się druga implementacja lub potrzeba izolacji sieci w testach.

Musi dotrzymywać obietnic kontraktu nie tylko pod względem sygnatur metod, ale też ich zachowania. Jeśli jedna bramka zwraca status „zaakceptowano” po potwierdzeniu, a druga oznacza nim tylko przyjęcie żądania, nie są wymienne, nawet gdy zwracają ten sam typ PaymentResult.

Jeśli reguły, takie jak rabaty, regularnie się zmieniają, osobne implementacje strategii mogą pozwolić dodawać nowe warianty bez rozbudowy długiego łańcucha warunków. Gdy reguła jest krótka i stała, dodatkowa hierarchia typów może utrudnić czytanie. Abstrakcję warto zostawić, gdy oddziela powód do zmiany lub ułatwia istotny test.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

solid
interfejsy
dziedziczenie
wstrzykiwanie zależności
testy jednostkowe
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz