SOLIDny tydzień – I jak Interface Segregation

Jako, że dzisiaj piątek to jest to czas na luźniejszą regułę z grupy SOLID. Dzisiaj na tapecie mamy literę I – Interface Segregation Principle, a więc zasadę segregacji interfejsów.

W skrócie

Reguła segregacji interfejsów mówi po prostu, że interfejsy powinny być małe i konkretne, tak aby klasy nie musiały implementować metod, których nie potrzebują. Tak więc koniec z uber-interfejsami, czas na minimalizm.

Czyli, że co?

W temacie segregacji interfejsów myślę, że dobrym przykładem z życia będzie temat przewodów. Wyobraź sobie, że producenci wymyślili super-przewód do komputerów, który zawiera wszystkie możliwe sposoby przesyłu danych i pozwala się wpiąć do wszystkiego. 50-pinowe gniazdko, kabel grubości palca, 18 wtyczek na drugim końcu. Cudo. Co prawda sporo komputerów nie wykorzystuje niektórych wyjść/wejść, np. nie wspierając wyjścia VGA, a jedynie HDMI, ale skoro są w przewodzie to da się podłączyć. Czy jest to wygodne? Absolutnie. Nie dość, że sam przewód w wersji >1m jest sprzedawany z osobną walizką do transportu bo waży tyle co laptop to dodatkowo trzeba się za każdym razem zastanawiać czy wtyczka, którą chce się użyć jest obsługiwana przez komputer i nie wisi wyłączona. Masakra. I to jest przykład tego uber-interfejsu, który zawiera wszystkie potencjalnie potrzebne metody.

Dlatego właśnie w komputerach jest tyle wyjść i wejść różnego typu żeby każdy producent i użytkownik mógł dobrać zestaw pod siebie. Ponieważ producent laptopa wybrał taki, a nie inny zestaw złączy to możemy być pewni, że każde z nich zostało obsłużone i na pewno możemy z niego skorzystać. W dodatku przewody są lekkie i poręczne. Da się ich również użyć w innych urządzeniach niż sam komputer. I to są właśnie te nasze małe interfejsy, o które walczymy. Przy okazji ładnie się tutaj wpasowuje reguła pojedynczej odpowiedzialności, którą omawiałem jako pierwszą – każdy interfejs odpowiada za konkretną funkcjonalność.

Przykłady

Jednym z przykładów dla segregacji interfejsów jest implementacja kolekcji w C#. Jeśli zdarzyło Ci się przekazywać gdzieś zbiory danych do odczytu lub zapisu to na pewno spotkałeś się z interfejsami IEnumberableICollection. I być może zauważyłeś (jeśli nie to możesz sprawdzić), że np. Lista implementuje oba te interfejsy bo pozwala na zapis i odczyt danych z niej. Jednak w przypadku kiedy chcemy mieć klasę pozwalającą tylko na odczyt danych po jej utworzeniu (np. tablica) to nie musimy implementować metod do dodawania, zostawiając je puste, bo wystarczy nam interfejs IEnumerable. A dzięki temu, że jest to ten sam interfejs co dla listy to możemy napisać metodę, która będzie potrafiła czytać zarówno z tablicy jak i z listy nie przejmując się który konkretnie typ dostała.

Załóżmy, że mamy w naszej aplikacji raporty. I dla tych raportów przygotowaliśmy interfejs:

Teraz implementujemy różnego rodzaju raporty:

Wszystko fajnie tylko, że jeden z raportów nie potrzebuje metody do drukowania na drukarce, ma być tylko zapisany na dysku. Ale ponieważ nasz interfejs do raportów zawiera wszystko „co może się przydać” to również ta metoda musiała być jakoś obsłużona. Dlatego rzuca zgodnie z prawdą wyjątek o braku implementacji.

A wystarczyło rozdzielić ten interfejs na mniejsze i pozwolić na ich niezależną implementację:

Dzięki temu nikt nie musi się zastanawiać czy użycie jakiejś metody nie spowoduje rzucenia wyjątku. Daje to też możliwość używania takich interfejsów dla innych klas. Np. IPrintable może być użyty dla wszystkich klas, które mogą być wydrukowane więc możemy na jego podstawie stworzyć kolejkę drukowania nie zastanawiając się co konkretnie ma być wydrukowane.

Dodaj komentarz

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