W poprzednich wpisach poświęconych SOLID, niejednokrotnie pojawiały się wzmianki o interfejsach. Osobiście jestem ich gorącym zwolennikiem, ale wszyst

W poprzednich wpisach poświęconych SOLID, niejednokrotnie pojawiały się wzmianki o interfejsach. Osobiście jestem ich gorącym zwolennikiem, ale wszystkiego trzeba używać z głową. Tego nauczy Was życie.. i w tym przypadku właśnie czwarta zasada z mnemoniku SOLID, która opisuje zasadę segregacji interfejsów (z ang. Interface segregation principle).    Na początek warto sobie przypomnieć podstawowe cechy interfejsów, a jest ich kilka: - Interfejsy zawierają tylko deklaracje metod oraz właściwości - Każdy element zdefiniowany w interfejsie winien być zaimplementowany w docelowej klasie - Interfejsy mogą rozszerzać inne interfejsy - Klasa może implementować kilka interfejsów   Jak widać niosą one ze sobą sporo możliwości i tym samym łatwo doprowadzić do ich nieprawidłowego użycia. Zasada ISP mówi o tym, jak powinniśmy z nich korzystać prawidłowo.   Interface Segregation Principle  Czy zdarzyło Wam się kiedyś napisać taki interfejs, który nie był „w pełni” implementowany w klasach docelowych? Czyli np. utworzyliście interfejs zawierający pięć metod, ale istniała pewna klasa, która implementowała tylko trzy z nich, a pozostałe zostawiała puste? Jeśli tak to pogwałciliście zasadę Interface Segregation Principle, która brzmi:   Many client-specific interfaces are better than one general-purpose interface. - co w wolnym tłumaczeniu znaczy:   "lepiej używać wielu specjalizowanych interfejsów per klient, niż jednego ogólnego".   W takiej sytuacji lekiem na wspomniany problem, byłoby podzielenie jednego dużego interfejsu na dwa mniejsze. Pierwszy zawierałyby deklaracje trzech metod, które byłyby implementowane we wszystkich klasach, drugi natomiast definiowałby by deklaracje pozostałych dwóch metod, które nie były wykorzystywane wszędzie. W docelowych klasach implementowano by jeden, lub dwa interfejsy w zależności od aktualnych potrzeb.   Alternatywnym rozwiązaniem byłoby dziedziczenie interfejsów, które również warto rozważyć.   Przykład praktyczny W przykładzie będziemy się posiłkować kodem, który powstał przy okazji wpisów na temat SRP, a później OCP (drugi wpis o SOLID). Jak z pewnością pamiętacie, wykształciliśmy tam interfejs IFileWriter, który prezentował się w następujący sposób: [sourcecode language="csharp"] public interface IFileWriter { void Open(); void WriteData(IEnumerable<string> data); void Close(); } [/sourcecode] Był on później implementowany przez klasy CsvFileWriter oraz PdfFileWriter. Wyobraźmy sobie teraz, że zaszła potrzeba ustawiania separatora dla plików CSV. Przydałaby się więc nam na to nowa metoda. W tym momencie mogło by nas pokusić, by dodać nową metodę do istniejącego interfejsu: [sourcecode language="csharp"] public interface IFileWriter { void Open(); void WriteData(IEnumerable<string> data); void Close(); void SetDelimiter(char delimiter); } [/sourcecode]   Oczywiście automatycznie kod przestałby się kompilować, ponieważ obie nasze klasy wymagałaby implementacji tej metody. Dla zachowania spójności, w klasie PdfFileWriter wprowadzilibyśmy pustą implementację: [sourcecode language="csharp"] public class PdfFileWriter : IFileWriter { // dotychczasowa implementacja public void SetDelimiter(char delimiter) { // nic nie robię... } } [/sourcecode]   Taki kod oczywiście się skompiluje.. ale warto sobie zadać pytanie, czy nie można tego zrobić lepiej i czy na pewno jest to najbardziej optymalne rozwiązanie, zgodne z zasadami sztuki? Osobiście tak nie uważam. Dużo lepszą opcją będzie utworzenie drugiego interfejsu, który zawiera nową metodę potrzebną tylko i wyłącznie w przypadku plików CSV: [sourcecode language="csharp"] public interface ICsvFileWriter { void SetDelimiter(char delimiter); } [/sourcecode]   Teraz możemy zmodyfikować klasę CsvFileWriter w taki sposób, byimplementowała oba interfejsy [sourcecode language="csharp"] public class CsvFileWriter : IFileWriter, ICsvFileWriter { private char _delimiter; // dotychczasowa implementacja public void SetDelimiter(char delimiter) { _delimiter = delimiter; } } [/sourcecode]   W prosty sposób naprawiliśmy problem łamania zasady Interface Segregation Principle. Na przyszłość warto pamiętać o tym, co umieszczamy w naszych interfejsach oraz o tym że powinny być one ogólne i nie skręcać w kierunku zaspokojenia potrzeb konkretnych klas.    

Jerzy Piechowiak

Altcontroldelete.pl

 

 Szukasz książki do C#? Kliknij TU lub zerknij poniżej: