InterfejsCzęść NR.13Wcześniej dziedziczyliśmy z klas do klas jednak popularnym mechanizmem w dziedziczeniu są Interfejsy.

Interfejs nie zawiera żadnego kodu użycia natomiast zawiera tylko specyfikacje metod i jej właściwości. 

Interfejsy można traktować jak kontrakt, który gwarantuje ,że dana klasa lub struktura, która po nim dziedziczy obsługuje dane zachowania. .

Kiedy klasa obsługuje dany interfejs musi udostępniać wszystkie elementy tego interfejsu.

Dla programistów Java
Język C# nie pozwala na stosowanie pól stałych w interfejsach. Najbliższym odpowiednikiem tego mechanizmu są stałe wyliczeniowe. W C++ nie mamy interfejsów są tylko klasy mieszane.

Definiowanie interfejsów

Użyjmy jako przykładu interfejsu IComparable.

namespace System
{
    public interface IComparable
    {
       int CompareTo(object obj);
    }
}

Aby zadeklarować interfejs używamy słowa kluczowego interface analogicznie tak, jak class czy struct. Wewnątrz interfejsu deklarujemy metody bez modyfikatora dostępu. A ciała metod nie mogą być uzupełnione więc zamiast nawiasów klamrowych stawiamy średnik “;”

Nazwy Interfejsów w .NET
Microsoft zaleca aby interfejsy zaczynały się od litery “I”. Jak sam możesz się przekonać za pomocą Intellisense wbudowane interfejsy zaczynają się od litery “I”.

[Interjesy-w-.NET4.png]

Warto jednak pamiętać , że ta zasada nie przekłada się do innych języków programowania jak Java.


Implementowanie Interfejsów

Podobnie jak w przypadku dziedziczenia po klasie, implementowanie dziedziczenia interfejsu działa tak samo.

class Test :IComparable
{
    public int CompareTo(object obj)
    {
         throw new NotImplementedException();
    }
}

Klasa bądź struktura, która dziedziczy po interfejsie musi implementować wszystkie jej metody w przeciwnym wypadku zobaczymy błąd.

Interfejs i klasa musi implementować wszystkie

Z tego powodu Interfejsy nie posiadają dużej ilości metod, jest to nawet niezgodne z jedną z zasad S.O.L.I.D .Interfejsy nie mogą być grube i udostępniać wszystko do wszystkiego.

Odejdę od tego gotowego przykładu i wykażę się trochę większą inicjatywą.

Oto interfejs IRuchPoZiemi w teorii prawie wszystkie ssaki poruszają się po lądzie. A z drugiej strony ten interfejs może być użyteczny też dla płazów i gadów.

interface IRuchPoZiemi
{
    int LiczbaNog();
}

Użyjmy tego interfejsu dla psa.

class Pies : Ssak,IRuchPoZiemi
{
    public override string ToString()
    {
    return "Jestem Psem";
    }

    public int LiczbaNog()
    {
    return 4;
    }
}

Pamiętaj , że klasa może mieć tylko jedną klasę bazową ,ale klasa może mieć wiele interfejsów o czym dalej w tym wpisie.

Metoda implementowana przez interfejs musi:

  • Mieć taką samą nazwę i zawracać dokładnie tę samą wartość.
  • Każdy parametr musi się zgadać.
  • Nazwa metody jest poprzedzona przez nazwę interfejsu. Nazywa się to "explicity interface implementation".
  • Wszystkie metody implementowane muszą być publiczne , jeśli metody są implementowane w stylu “explicity interface implementation” to metoda nie powinna posiadać kwalifikatora dostępu.


Jeśli jest jakaś różnica to kompilator nie skompiluje kodu.

Na szczęście Visual Studio pomaga w implementacji interfejsów. Często nie pamiętamy, która metoda jak się nazwa, co zwraca i jakie ma parametry.

Pomoc Visual Studio

Jest to mała ikonka, która pojawia się po napisaniu interfejsu i po najechaniu na nią myszką pojawia się chmurka pomocnicza. Jestem też pewny, że istnieje jakiś skrót klawiszowy, który robi dokładnie to samo, ale w tej chwili nie mogę sobie tego przypomnieć. Jak widzisz w tej pomocniczej chmurce mamy do dyspozycji dwa style implementacji interfejsu. Opowiem o tym później , ale najpierw…

Referowanie się do klasy poprzez interfejs

Jak pamiętamy obiekt można przypisać do zmiennej, która jest klasą bazową w stosunku do obiektu. W ten sam sposób możesz przypisać obiekt klasy do interfejsu.

Oto przykład , że obiekt pies może być przypisany do interfejsu “IRuchPoZiemi”

Pies pies = new Pies();
    IRuchPoZiemi rpz = pies;

Zapis ten jest poprawny, ponieważ pies dziedziczy po interfejsie IRuchPoZiemi. Tak jak z przypisaniem do klas bazowych, by przywrócić obiekt z interfejsu do swojej oryginalnej formy trzeba wykonać rzutowanie, bo w końcu obiekt przypisany do tego interfejsu niekoniecznie jest psem.

Mając obiekt przypisany do interfejsu można skorzystać tylko z metod udostępnianych przez interfejs.

Ten mechanizm jest użyteczny, ponieważ możesz np. zadeklarować metodę, która w parametrach będzie potrzebowała tego interfejsu.

int MetodaBieganie(IRuchPoZiemi iruchpoziemi)
    {
    return iruchpoziemi.LiczbaNog();
    }

Metoda ta wykona czynność X dla wszystkich obiektów implementujących ten interfejs (IRuchPoZiemi). Przy czym każdy inny obiekt klasy ma swoją własną implementacje tej metody (LiczbaNog()) . Czynność X powinna zawsze się wykonać poprawnie dla wszystkich obiektów różnych klas ,ale z tym samym interfejsem.

Wiele interfejsów

Klasa może mieć tylko jedną klasę bazową ,czyli dziedziczyć tylko po jednej klasie. Interfejsy łatają tą lukę. Nie ma określonego ograniczenia co do liczby interfejsów, które może przyjąć klasa.

Interfejs, struktura czy klasa może dziedziczyć po kilku interfejsach.

class Pies : Ssak,IRuchPoZiemi,IAaaaaaa,IBbbbbbb,ITest,IJaMamSens

Każdy następny interfejs musi być oddzielony przecinkiem.

Ponieważ interfejsów może być dużo warto dbać o to, by były one chude i zgodne z zasadami S.O.L.I.D.

Explicite vs implicit - Disambiguating Methods

Ok., więc możemy na dwa sposoby zaimplementować interfejs, jakie są zalety i wady tych sposobów. O tym właśnie tej części.

Jak wygląda implementacja interfejsu w stylu explicite. Wygląda ona tak.

class Pies : Ssak,IRuchPoZiemi
    {
    int IRuchPoZiemi.LiczbaNog()
    {
    return 4;
    }
    }

Jaka jest różnica pomiędzy tą metodą  a inną i kiedy którą warto stosować?

Skoro klasa może dziedziczyć po wielu interfejsach może powstać problem z powtarzaniem się nazw metod, wtedy warto zastosować implementacje Explicite.

Sprawdziłem słowo Explicte i ono po francusku oznacza “dokładny” ,a po angielsku wyraźny. Postanowiłem jednak trzymać się angielskiego słowa i go nie tłumaczyć.

W czym dokładnie jest problem.

public interface IAaaaaaa
    {
    string Z();
    }
    public interface IBbbbbbb
    {
    string Z();
    }

    public class Ccc: IAaaaaaa,IBbbbbbb
    {
    public string Z()
    {
    return "Ccc";
    }
    }

Oba interfejsy implementują metodę “Z” nie ma konfliktu nazw i aplikacja się skompiluje.

Dlaczego? Kompilator uznaje ,że wszystko jest w porządku w końcu klasa implementuje metodę Z.

Jednak w pewnym sensie dwie różne metody w obu interfejsach będą wywoływać ten sam wynik , a może byśmy tego nie chcieli.

Oto co się stanie, jeśli przypiszemy obiekt klas C na interfejsy IAaaaaa i na IBbbbbbb.

Ccc c = new Ccc();

    IAaaaaaa a = c;
    IBbbbbbb b = c;

    string[] wynik = new string[3];
    wynik[0] = c.Z();
    wynik[1] = a.Z();
    wynik[2] = b.Z();

Wynik kodu:

Interfejs

Właśnie w takich wypadkach interfejsy dokładny “explicity” jest użyteczny. Chcemy koniecznie aby metoda Z() dla różnych przypisań dała inny wynik. Przykład poniżej ilustruje wykorzystanie dokładnej implementacji, jeśli chcemy aby dla interfejsu IBbbbbbb wynik był inny.

public class Ccc: IAaaaaaa,IBbbbbbb
    {
    public string Z()
    {
    return "Ccc";
    }

    string IBbbbbbb.Z()
    {
    return "Bbb";
    }
    }


Oto wynik poprzedniego kodu, gdy interfejsy są zaimplementowane w taki sposób.

Interfejs 2

Jeśli oba interfejsy są zaimplementowane w stylu explicite. Nie będziemy mogli wywołać metody dla samej klasy Ccc, ponieważ nie są one dla niego dostępne. O tym fenomenie w dalszej części wpisu.

Użycie metod dokładny interface

Interfejs 3

Ma to jednak sens, ponieważ, którą metodę miałaby klasa Ccc uruchomić i z którego interfejsu.

Oto kod z dwoma interfejsami zaimplementowanymi w stylu explicite.

public class Ccc: IAaaaaaa,IBbbbbbb
    {
    string IAaaaaaa.Z()
    {
    return "Aaa";
    }

    string IBbbbbbb.Z()
    {
    return "Bbb";
    }
    }


Interfejs 4

Oczywiście nic nie stoi na przeszkodzie abyśmy mogli zaimplementować metody z interfejsów w obu stylach. Chociaż na pewno łamie to jakieś zasady obiektowości i lepiej tak nie robić.

npublic class Ccc: IAaaaaaa,IBbbbbbb
    {
    public string Z()
    {
    return "Ccc";
    }

    string IAaaaaaa.Z()
    {
    return "Aaa";
    }

    string IBbbbbbb.Z()
    {
    return "Bbb";
    }
    }
Interfejs 5

Dzięki temu nasze metody o tych samych nazwach , ale z różnych interfejsów mogą zachowywać się inaczej.

Explicite vs implicit – Ukrywanie metod

Zastosowanie explicite ma też sens nawet wtedy, gdy nie występują niejasności co do powtarzających się metod.

Dzięki takiej implementacji możemy ukrywać metody . Metody te są prywatne dla klasy jej implementującej . Chociaż słowo prywatne nie jest tutaj na miejscu, ponieważ sama klas też nie ma dostępu do tej metody. Używając normalnej implementacji interfejsu nasza metoda zawsze jest publiczna, więc jest to jedyny sposób na blokadę dostępności metody z interfejsu.

Czy ma to sens? Oczywiście. Oto przykład implementacji interfejsu IDisposable, która posiada jedną metodę Dispose(). Metoda ta ogólnie powinna wykonać wszystkie czynności aby usunąć informacje o obiekcie z pamięci komputera.

public class MyFile :IDisposable
    {
    void IDisposable.Dispose()
    {
    CloseFile();
    }

    public void CloseFile()
    {
    //Zamykanie pliku GC.SuppressFinalize(this);
    }
    }

Wzór takiego zachowania występuje w klasach .NET. Klasy, które implementują interfejs IDisposable w większości nie mają metody “Dispose()” na widoku (nie znam wszystkich klas to powiem w “większości”). Co prawda ten kod powyżej nie ma nic wspólnego z dobrą implementacją IDisposable to tylko przykład.

Jak napisałem wcześniej , aby mieć dostęp do tej metody musimy przypisać obiekt do interfejsu IDisposable, jeśli chcemy użyć tej metody.

MyFile my = new MyFile(); IDisposable dis = my; dis.Dispose();

Explicite vs implicit – Podsumowanie

Podsumowując metody zaimplementowane Explicite są dostępne tylko wtedy, gdy przypiszemy je do interfejsu.

Oto parę istotnych informacji w pigułce na temat Explicite:

  • Nie używamy implementacji Explicite jako bariery bezpieczeństwa. Metoda ta wciąż może być użyta, jeśli obiekt zostanie przypisany do interfejsu.
  • Używamy go wtedy, gdy chcemy ukryć implementacje metody.
  • Używamy go, gdy chcemy aby metoda zachowywała się jak prywatna.
  • Podobno pakowanie (boxing) zachodzi w czasie używania interfejsów Excplite dla typów wartościowych więc użycie go w takim wypadku zmniejsza sprawność kodu.
  • Stosujemy go, gdy istnieje konflikt nazw metod i chcemy je oddzielić zachowaniem.


Najczęściej zwykła implementacja interfejsu jest najczęściej spotykana. Jednak jeśli istnieje taka potrzeba zaleca się użycie implementacji interfejsu w stylu Explicite.

Zasady używania interfejsów

A teraz ogólne zasady używania interfejsów, jeśli nie wychwyciłeś ich wcześniej:

  • Nie możesz definiować pól w interfejsie, nawet pól statycznych.
  • Nie możesz zdefiniować konstruktora i destruktora wewnątrz interfejsu.
  • Nie możesz dać modyfikatora dostępu dla interfejsów, wszystkie są automatycznie publiczne.
  • Nie możesz zagnieździć klasy, struktury, typów wyliczeniowych i innych interfejsów wewnątrz interfejsu.
  • Interfejs nie może dziedziczyć po klasie. Chociaż interfejs może dziedziczyć zachowanie po innym interfejsie. Dziedziczenie po klasie nie może zajść, ponieważ zawiera ona implementacje kodu i to łamie główną zasadę interfejsów.