C#: Interfejsy

Jeżeli uznamy, że klasa jest szablonem dla obiektu, to interfejs jest szablonem dla klasy.

Weźmy taki przykład:

Mąż/Żona wysyła cię do sklepu po artykuły na obiad. Ma być makaron z sosem pomidorowym. Na liście zakupów masz dwie pozycje: makaron i pomidory. Nie wiadomo, o jaki dokładnie makaron chodzi (spaghetti czy świderki?) ani w jakiej postaci mają być pomidory (świeże czy przecier?). Prawda jest jednak taka, że bez względu na to którą opcję wybierzesz, taki artykuł spełni swoją rolę.

Podobnie jest z interfejsami. Interfejs gwarantuje nam to, że klasa, która go implementuje, posiada pewne określone cechy (właściwości, metody).

Przepraszam za polskie znaki w kodzie, ale przykład jest głównie poglądowy, tak się go po prostu łatwiej czyta.

Popatrzmy teraz na poniższy przykład. Mamy zdefiniowane dwa interfejsy. Pierwszy jest pusty (może taki być, nie ma problemu), drugi natomiast ma sygnaturę jednej metody.

Teraz stwórzmy sobie kilka klas, które będą te interfejsy implementować. Klasy, które implementują dany interfejs, muszą implementować wszystkie jego składniki – w naszym przypadku jest to metoda CzasGotowania() dla interfejsu IMakaron.

Przydało by się to wszystko złożyć razem. Napiszmy prostą klasę, która “robi obiad” z podanych przez nas składników.

Jak widać na powyższym przykładzie, wykorzystujemy metodę CzasGotowania() dostępną dla parametru makaron. Nie wiemy, jaka dokładnie klasa zostanie przekazana do parametru (może Spaghetti, może Świderki, ale równie dobrze może być też taka, którą stworzymy dopiero później), ale mamy pewność, że będzie miała tą metodę (właśnie dzięki interfejsowi).

I jeszcze wykorzystanie (aplikacja konsolowa):

 

Co nam dają interfejsy?

Gwarantują, że każda klasa, która implementuje dany interfejs, posiada określone cechy, z których możemy korzystać. Przykładowo zarówno listy, słowniki jak i tablice implementują interfejs IEnumerable, dzięki czemu możemy iterować po kolejnych elementach tych obiektów np w pętli foreach.

Inaczej rzecz ujmując, można powiedzieć, że wszystkie klasy, które implementują dany interfejs, mają część wspólną – wynikającą właśnie z konieczności zaimplementowania wszystkich składników interfejsu. Co z tego wynika? Ano właśnie to, że metoda jako parametr może przyjmować obiekt typu interfejsowego (IMakaron) zamiast konkretnej klasy (np Spaghetti). Daje to więcej swobody i możliwości w tworzeniu klas.

 

Kilka cech interfejsów:

  • Przyjęło się mówić, że klasa dany interfejs implementuje (a nie po nim dziedziczy)
  • Klasa może implementować wiele interfejsów (ale dziedziczyć po tylko jednej innej klasie)
  • Każda klasa implementująca dany interfejs będzie miała ten sam zestaw właściwości i metod (te same nazwy, parametry i typy zwracane), ale każda będzie mogła mieć własną implementację danej metody
  • Konwencja mówi, że nazwa interfejsu powinna się zaczynać od dużej litery “i” – IEnumerable, IQueryable, IDisposable itd.
  • Tworząc interfejs, nie określamy poziomu dostępności składników – wszystkie domyślnie są publiczne (i takie muszą być w klasach docelowych)
  • Tworząc interfejs, nie piszemy implementacji metod, tylko same sygnatury (typ zwracany + nazwa + opcjonalnie przyjmowane parametry)
  • Tworząc interfejs, możemy zdefiniować właściwości, metody, zdarzenia oraz indeksatory
  • Tworząc interfejs, NIE możemy zdefiniować pól, konstruktorów i destruktorów, ani zagnieżdżać innych klas, struktur, interfejsów, typów wyliczeniowych
  • Nie można utworzyć obiektu interfejsu za pomocą operatora new

Na koniec zróbmy jeszcze jeden myk. A gdyby tak kupić gotowe danie w słoiku? Mniej roboty, prawda?

Działa? Działa!