SOLID – Zasada segregacji interfejsów

Kolejnym elementem układanki, który komponuje mnemonik SOLID jest Zasada segregacji interfejsów (Interface segregation principle). Jeśli jeszcze nie wiesz, co kryje pod sobą akronim SOLID, zapraszam do odpowiedniego artykułu.

SOLID - Zasada otwarte-zamknięte

SOLID – Zasada segregacji interfejsów

Zasada segregacji interfejsów

Omawiana reguła idealnie komponuje się we wcześniej już poznane zasady SOLID oraz w bliżej niezdefiniowane wyczucie logicznego programowania, które każdy posiada.

Przytaczając definicje umieszczoną na stronie Wikipedii.

Klienci nie powinni zależeć od interfejsów, których nie używają.

Myślę, że bardzo przystępnie, ale kolokwialnie ujmując, zbyt ogólnie, aby z doskoku odpowiednio ją zinterpretować.

Powstrzymując się jeszcze od przykładu, wyjaśnię powiązanie Zasady segregacji interfejsów w szerszym kontekście.

Niniejsza zasada skłania do bycia zwięzłym w definiowaniu pojedynczych kontraktów, czyli interfejsów. Tworzone interfejsy powinny zawierać minimalną liczbę deklaracji metod, a same metody powinny być są ze sobą ściśle powiązane poprzez obszar swojej funkcjonalności.

Postępowanie zgodnie z tą zasadą z pewnością usprawni wiedza o Zasadzie pojedynczej odpowiedzialności, która mówi o tym, że klasa powinna składać się z metod działających w jednym obszarze oraz powinien istnieć tylko jeden powód do zmiany definicji klasy, np. jeżeli zmiana w wykonywaniu raportów i zmiana w obrębie formatu wydruku raportów zaprowadziłaby programistę do jednej i tej samej klasy, to nie można mówić o stosowaniu Zasady pojedynczej odpowiedzialności.

Dodatkowym elementem jest świadomość o wielodziedziczeniu interfejsów języka C#. Zachodzi ono za równo w stosunku do klas, jak i samych interfejsów. Oznacza to, że lepiej tworzyć mniejsze kontrakty danych, niż jeden ogromny, tzw. fat, który zmusza programistę do każdorazowego dostarczania definicji dla wszystkich metod, które deklaruje interfejs. Zwięźlejsze interfejsy są łatwiejsze w wielokrotnym używaniu i nie przyczyniają się „puchnięcia” kodu. Wielodziedziczenie:

Przykład

Pozostańmy przy raportach. Załóżmy, że jesteśmy już w posiadaniu interfejsu, który definiuje podstawową funkcjonalność pewnych raportów.

Powyższy kontrakt wymusza podstawową definicję dla przyszłego obiektu.

W nasze ręce, a w zasadzie palce wpadło zadanie, które dostarcza możliwość automatycznego uruchamiania raportów. Postanowiliśmy, że będzie za to odpowiedzialna jakaś klasa bliżej niezdefiniowanego Schedulera, na potrzeby przykładu jest to wystarczające założenie. Moglibyśmy to zrobić poprzez rozszerzenie interfejsu IBaseRaport w ten sposób:

Jednakże, w jakim celu klasa odpowiedzialna za raporty miałaby implementować funkcjonalności Schedulera? Zmusiłoby to do dostarczenia definicji dla nowo utworzonych metod, które zapewne w klasie Raport skończyłyby z taką definicją.

Dlatego rozwiązaniem zgodnym z Zasadą segregacji interfejsów, byłoby stworzenie nowego kontraktu IScheduler, przeznaczonego dla klasy Schedulera. Takie rozwiązanie utrzymuje odrębność i niezależność kontraktów:

Dodatkowo metody zawierające throw new NotImplementedException() łamią omawianą poprzednio Zasadę podstawienia Liskov. Dlatego egzystencja tego rodzaju tworów powinna być silnym sygnałem, alarmującym o złamaniu ISP i potrzebie stworzenia nowego interfejsu.

A co w przypadku, gdy definicje interfejsu dostajemy z zewnątrz?

Stoją przed nami dwoje drzwi. Pierwsze prowadzą do stworzenia własnego interfejsu. Natomiast drugie do zastosowania wzorca adapter, który dostarczy nam „przejściówkę” z wszystkimi potrzebnymi funkcjonalnościami.

Podsumowanie

Zasada segregacji interfejsów wymusza budowanie wielu krótkich i zwięzłych interfejsów, zamiast rozbudowanych gigantów, które dostarczają wielu problemów i niejasności, gdy jesteśmy do ich wielokrotnego użycia.

Niewątpliwie w poprawnym definiowaniu zawartości interfejsu pomaga wejście w perspektywę klienta i próba określenia jego oczekiwań oraz wymagań.

Więcej o wzorcu SOLID w artykule.

Dodaj komentarz