SOLIDny tydzień – O jak Open-Closed

Dzisiaj bierzemy na warsztat drugą literę z ze zbioru SOLID czyli ‚O’ jak Open-Closed. Regułę prostą ale bardzo ważną zwłaszcza w większych projektach.

W skrócie

Open-closed czyli ‚otwarty-zamknięty’ jest regułą głoszącą, że klasa jest otwarta na rozbudowę ale zamknięta na modyfikację. Tak brzmi oficjalna definicja, którą sobie zaraz omówimy.

Druga litera zbioru SOLID, tak jak wspomniałem na wstępie jest jedną z prostszych, jeśli nie najprostszą z reguł. Dzięki temu jej stosowanie nie stanowi problemu nawet dla osób mało doświadczonych. Jednak nie zawsze są one świadome korzyści z tego płynących. A dopiero świadome korzystanie z zasad daje wymierne efekty i widocznie poprawia jakość.

Czyli, że co?

Mimo, że definicja Open-Closed brzmi lekko zagmatwanie to jest ona jak najbardziej logiczna. Najprościej rzecz ujmując chodzi o to, że rozbudowa klasy o obsługę nowych elementów powinna być możliwa bez konieczności zmieniania czegokolwiek w samej klasie. Niewykonalne? A co jeśli Ci powiem, że prawdopodobnie spotkałeś się z przedmiotami „implementującymi” tą zasadę w rzeczywistości?

Idealnym przykładem jest tutaj komputer stacjonarny. Możesz mu zmienić kartę graficzną albo dołożyć więcej pamięci RAM lub zamienić dysk z HDD na SSD bez konieczności grzebania w płycie głównej w celu dostosowania jej do innego modelu. Po prostu wymieniasz co trzeba korzystając z dostępnych i zgodnych interfejsów i fizycznie wszystko działa.

Dokładnie tak samo powinno być z Twoimi klasami – wkładasz inny element zgodny z interfejsem i wszystko działa. Tylko tyle i aż tyle.

Częstym błędem jest zamykanie się na rozbudowę bez modyfikacji poprzez odwoływanie się w klasie bezpośrednio do innej konkretnej klasy. Przykładowo możemy mieć prostą klasę do obsługi płatności, która wysyła zapytanie do PayPala:

Teraz chcąc zmienić dostawcę płatności np. PayU musisz zmodyfikować klasę Payment tak żeby korzystała z innego serwisu, który będzie robił co prawda to samo ale będzie innego typu.

Sytuacja jeszcze bardziej się skomplikuje kiedy dasz użytkownikowi wybór przez co chce płacić. Bo co wtedy? Będziesz przyjmował kolejny serwis i dodawał kolejną metodę robiącą to samo tylko inaczej?

Zaczyna się robić dziwnie.

A może by tak interfejsik?

A wystarczyło żeby klasy Payment nie obchodziło jaki dokładnie dostawca jest użyty, byle zawierał pasujący interfejs. Dokładnie tak samo jak w komputerze – płyty głównej nie obchodzi czy włożyłeś kartę NVidii czy AMD, ważne żeby posiadała interfejs PCI-Express.

Dla powyższego przykładu można problem rozwiązać łącząc klasę Payment jedynie z interfejsem IPaymentProvider.

Albo przez konstruktor:

Albo przez parametr metody:

Zależnie od tego jak tworzysz i używasz obiekt klasy Payment.

Teraz, mając interfejs, klasa Payment może działać z PayPalem, który ten interfejs będzie implementował. Ale jeżeli zajdzie potrzeba zmiany dostawcy to po prostu go przekażesz, nie dotykając nawet jednego znaku w kodzie klasy Payment.

Oczywiście zamiast interfejsu możesz użyć w razie potrzeby klasy abstrakcyjnej albo po prostu klasy bazowej jednak w 95% przypadków interfejs jest wystarczający i najbardziej uniwersalny, zwłaszcza w języku C#.

 

O poprzedniej regule możesz przeczytać we wczorajszym wpisie.

 

A jeżeli nie chcesz przegapić żadnej z części i dostać w niedzielę maila podsumowującego z ich pełną listą to zapisz się do mojego newslettera:


4 thoughts on “SOLIDny tydzień – O jak Open-Closed”

  1.     private IPaymentProvider _paymentProvider;

        public Payment(PayPalService payPalService)
        {
            _payPalService = payPalService;
        }

    tutaj parametrem nie powinien być interfejs?

  2. To jeszcze tylko w:
    public void Pay(decimal amount, PayPalService payPalService)
        {
            // … something …
            paymentProvider.Request(amount);
        }

    Też powinien być interfejs :)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *