Strona główna > Przykłady > [C#] Interfejsy – z czym to się je?

[C#] Interfejsy – z czym to się je?

Podstawy

Jak najszybciej/najprościej wytłumaczyć czym jest interfejs? Interfejs, to taka jakby klasa, która deklaruje swoje składowe (głównie metody), ale ich nie implementuje, tzn. interfejs zawiera nazwę metody, ale nie posiada jej ciała z kodem. Nie można utworzyć obiektu, który będzie instancją jakiegoś interfejsu, ale interfejs może być zaimplementowany przez klasę. Jeśli klasa implementuje interfejs to znaczy, że zapewnia implementację wszystkich członków interfejsu, tzn. interfejs mówi klasie, że klasa musi posiadać określone metody, właściwości itd. Interfejs informuje jakich zachowań (implementacji jakich metod) będziemy oczekiwać od klasy. Klasa może implementować wiele interfejsów naraz. Koniec bełkotu, przejdźmy do przykładu 🙂

using System;

namespace InterfaceTest
{
	interface IKucharz
	{
		void Gotowanie();
	}

	class TypOsoby1:IKucharz
	{
		int Id {get;set;}
		string Imie {get;set;}

		public TypOsoby1(int id, string imie)
		{
			this.Id=id;
			this.Imie=imie;
		}

		public void Gotowanie()
		{
			Console.WriteLine (this.ToString()+" gotuje...");
		}

		public override string ToString()
		{
			return string.Format ("{1}",this.Id,this.Imie);
		}
	}

	class MainClass
	{
		public static void Main (string[] args)
		{
			TypOsoby1 adam = new TypOsoby1(1,"Adam");
			adam.Gotowanie();
		}
	}
}

Przykład pokazuje użycie wymyślonego interfejsu o nazwie IKucharz  składającego się z jednej metody Gotowanie(). Dobrym zwyczajem jest rozpoczynanie nazwy interfejsu od „I”. Zapis implementacji interfejsu przez klasę jest taki sam jak zapis dziedziczenia (linijka 10). IKucharz ma metodę Gotowanie(), a klasa TypOsoby1 implementuje ten interfejs, więc też musi posiadać metodę Gotowanie() o takiej samej sygnaturze (taki sam typ zwracany i takie same parametry). I ot cała filozofia 😉 . W metodzie Main tworzę obiekt klasy TypOsoby1 i wywołuję metodę Gotowanie(), która swój kompletny brak przydatności dowodzi przez wypisanie jakichś bzdur w konsoli 🙂

Po przykładzie znów odrobina teorii (mniej ważnej dla kogoś, kto pierwszy raz styka się z interfejsami). Interfejsy mogą się składać z właściwości, metod, zdarzeń oraz indekserów. Nie mogą zawierać stałych, pól, operatorów, konstruktorów, destruktorów oraz typów. Wewnątrz interfejsów nie używamy static, ani modyfikatorów dostępu (domyślnie wszystko jest publiczne – public). Struktury mogą implementować interfejsy. Gdy klasa bazowa implementuje interfejs, to klasa po niej dziedzicząca też ma niejawnie zaimplementowany interfejs. Interfejsy mogą po sobie dziedziczyć.

Kolejny bardziej praktyczny, prosty przykład można znaleźć we wpisie o płytkich i głębokich kopiach obiektów. Wykorzystuję tam gotowy interfejs z biblioteki .NET o nazwie ICloneable. Wymusza on zaimplementowanie w klasie metody Clone zwracającej typ Object. Warto zapaznać się z innymi .NETowymi interfejsami. Np. ostatnio musiałem zaimplementować w pewnej strukturze interfejs IComparable, abym mógł skorzystać z metody Sort() należącej do listy generycznej zawierającej obiekty tej struktury. Jak zaimplementować IComparable? Wpisujemy w google „IComparable msdn” i znajdujemy prosty przykład.

Ograniczenie widoczności metod, implementacja jawna i niejawna, polimorfizm

Spróbujmy teraz trochę rozwinąć nasz przykład z IKucharzem. Przykład może wydawać się zawiły, ale wbrew pozorom wcale taki nie jest. Komentarz pod kodem powinien rozwiać wszelkie wątpliwości.

using System;

namespace InterfaceTest
{
	interface IKucharz
	{
		void Gotowanie();
	}

	interface IKierowca
	{
		void Prowadz();
	}

	interface IMuzyk
	{
		void Graj();
	}

	interface IKoszykarz
	{
		void Graj();
	}

	abstract class Osoba
	{
		int Id {get;set;}
		string Imie {get;set;}

		public Osoba(int id, string imie)
		{
			this.Id=id;
			this.Imie=imie;
		}

		public virtual void Jedzenie()
		{
			Console.WriteLine(this.ToString()+" posila się");
		}

		public override string ToString()
		{
			return string.Format("{1}",this.Id,this.Imie);
		}
	}

	class TypOsoby1: Osoba,IKucharz,IKierowca
	{
		public TypOsoby1(int id, string imie) : base(id, imie){}

		public override void Jedzenie()
		{
			Console.WriteLine(this.ToString()+" je miód");
		}

		public void Gotowanie()
		{
			Console.WriteLine(this.ToString()+" gotuje");
		}

	    void IKierowca.Prowadz()
		{
			Console.WriteLine(this.ToString()+" prowadzi samochód");
		}
	}

	class TypOsoby2: Osoba,IKierowca,IMuzyk,IKoszykarz
	{
		public TypOsoby2(int id, string imie) : base(id, imie){}

		public override void Jedzenie()
		{
			Console.WriteLine(this.ToString()+" żuje pszczoły");
		}

	    void IKierowca.Prowadz()
		{
			Console.WriteLine(this.ToString()+" prowadzi autobus");
		}

		void IMuzyk.Graj()
		{
			Console.WriteLine(this.ToString()+" gra na klarnecie");
		}

		void IKoszykarz.Graj()
		{
			Console.WriteLine (this.ToString()+" gra w koszykówkę");
		}
	}

	class MainClass
	{
		public static void Main (string[] args)
		{
			TypOsoby1 os = new TypOsoby1(1,"Adam");
			os.Gotowanie();
			os.Jedzenie(); //adam jako TypOsoby1 może jeść

			IKucharz kucharz = (IKucharz)os;
			kucharz.Gotowanie(); //os może gotować jako TypOsoby1 i jako IKucharz
			kucharz.Jedzenie(); //błąd - IKucharz nie może jeść
			kucharz.Prowadz(); //błąd - IKucharz nie może prowadzić

			os.Prowadz();//błąd - os może prowadzić tylko jako IKierowca
			IKierowca kierowca = (IKierowca)os;
			kierowca.Prowadz(); //tak już działa

			TypOsoby2 os2 = new TypOsoby2(2,"Klara");
			os2.Graj(); //błąd - os2 może grać tylko jako muzyk lub jako koszykarz
			IMuzyk muzyk = (IMuzyk) os2;
			muzyk.Graj();
			((IKoszykarz) os2).Graj();

			Console.WriteLine ("---------------------------Kierowcy prowadzą");
			IKierowca [] kierowcaTab = new IKierowca[]{os,os2};
			foreach(IKierowca k in kierowcaTab)
			{
				k.Prowadz();
			}

			Console.WriteLine ("----------------------------Ludzie jedzą");
			Osoba [] osoby = new Osoba[]{os,os2};
			foreach(Osoba o in osoby)
			{
				o.Jedzenie();
			}

		}
	}
}

Na początku deklaruję cztery interfejsy. Każdy z nich zawiera jedną prostą metodę zwracającą void. Następnie tworzę abstrakcyjną klasę Osoba, która zawiera dwie właściwości: Id oraz Imie, wirtualną metodę Jedzenie() oraz przesłoniętą metodę ToString(). Kolejne dwie klasy: TypOsoby1 i TypOsoby2 dziedziczą po klasie Osoba i implementują różne interfejsy. To co znajduje się w środku metod nie jest istotne – wypisujemy tam jakieś bzdury w konsoli, aby później zobaczyć na własne oczy czy dana metoda rzeczywiście zadziałała.

Nie sposób nie zwrócić uwagi na takiego dziwoląga:

void IKierowca.Prowadz()

Nazwa interfejsu przed nazwą metody oznacza jawną implementację metody interfejsu. Czym to się różni od zwykłej (niejawnej) implementacji? Odpowiedź za chwilę.

Wreszcie sedno przykładu – metoda Main. Tworzymy nowy obiekt typu TypOsoby1 nazwany os. Bez problemów wywołujemy na obiekcie metody Gotowanie() oraz Jedzenie() . Zwróćmy uwagę, że metoda Gotowanie() musi znajdować się w klasie TypOsoby1, ponieważ klasa ta implementuje interfejs IKucharz. Skoro klasa implementuje taki interfejs, obiekt os możemy rzutować na typ IKucharz. Następnie wywołujemy metodę Gotowanie() na obiekcie kucharz – wszytstko działa tak jak powinno. Jednak korzystając z obiektu kucharz nie możemy już wywoływać innych metod klasy TypOsoby1. Mamy jedynie możliwość wywoływania metod należących do interfejsu IKucharz. Ogólnie rzecz biorąc – obiekt rzutowany na typ interfejsu może korzystać tylko z metod tego interfejsu.

Kolejna sprawa – nie możemy na obiekcie os wywołać Prowadz(), dlaczego? Powodem jest jawne zaimplementowannie metody w klasie TypOsoby1. Jak już wspominałem jawną implementację rozpoznajemy po nazwie interfejsu przed nazwą metody w klasie ( void IKierwca.Prowadz() ). Co więc musimy zrobić, aby użyć Prowadz() ? Oczywiście powinniśmy rzutować os na IKierowca – wtedy metoda działa bez problemu.

Następnie tworzymy obiekt os2 typu TypOsoby2. Zwróćmy uwagę, że TypOsoby2 implementuje interfejs IMuzyk z metodą Graj() oraz interfejs IKoszykarz z identycznie nazwaną metodą. Sposobem na ominięcie problemu kolizji nazw metod w klasie jest jawna implementacja obu interfejsów. Obiekt os2 „widzi” tylko metodę Jedzenie() . Aby skorzystać z innych metod, musimy przeprowadzić odpowiednie rzutowanie.

Dalej bonusowy przykład czegoś bardzo ważnego i użytecznego. Pewno nie jest to coś obcego osobie znającej klasy abstrakcyjne, mimo wszystko dobry przykład nie jest zły, a kod aż się o to prosił – polimorfizm. Wrzucam do tablicy obiekty różnych typów (TypOsoby1 i TypOsoby2), które zostaną zrzutowane na typ bazowy (raz na IKierowca, raz na Osoba). W pętlach wywołujemy metodę typu bazowego, jednak dla każdego obiektu działanie metody będzie inne. W wyniku działania pierwszej pętli zostanie wypisane:

Adam prowadzi samochód
Klara prowadzi autobus

Druga pętla wyświetli:

Adam je miód
Klara żuje pszczoły

I to już wszystko o czym chciałem dziś napisać.

Reklamy
Kategorie:Przykłady Tagi: , ,
  1. Anonim
    19 marca 2012 o 8:29 pm

    Super 🙂

  2. 19 lutego 2013 o 11:40 am

    1.”Interfejsy mogą po sobie dziedziczyć.”
    Nie nazwałbym tego dziedziczeniem, jeśli byłoby tak to w przypadku
    interface bazowy
    {
    void pokaz();
    }
    interface pochodny
    {
    void wyswietl();
    }
    dałoby się dostać do metody w klasie bazowej
    pochodny interf = new pochodny();
    interf.pokaz(); // czego nie da się zrobić można tylko poprzez pełną nazwę interfejsu, w tym wypadku należałoby
    bazowy interf_2 = new bazowy();
    bazowy.pokaz();

    2. TypOsoby1 os = new TypOsoby1(1,”Adam”);
    IKucharz kucharz = (IKucharz)os;

    Klasa TypOsoby1 dziedziczy interfejs Ikucharz, więc nie trzeba rzutować, nie wklepywałem w kompilator, ale obiekt klasy pochodnej może być przypisany do obiektu klasy bazowej.

  3. 19 lutego 2013 o 11:41 am

    w poprzednim komentarzu wkradł się błąd, brakuje dziedziczenia w interfejsie interface pochodny: bazowy

  4. 14 kwietnia 2013 o 6:45 pm

    Inny przykład na fakt, że dziedziczenie w przypadku interfejsów nie jest właściwym słowem:
    w przypadku jawnej implementacji interfejsu niezbędne jest odwołanie się poprzez w pełni kwalfikowaną nazwę:

    interface Ibase
    {
    void show();
    }

    interface Iderived: Ibase
    {
    void show_me();
    }

    class my_class: Iderived
    {

    void Iderived.show_me()
    {

    }

    void Ibase.show()
    {

    }
    }
    gdyby występowało dziedziczenie metodę show() z interfacu Ibase można by zaimplementować poprzez odwołanie Iderived.show(), gdyż byłaby składowym elementem interfejsu Iderived, zaś tu tak nie jest

  1. 25 listopada 2011 o 3:22 pm

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s

%d blogerów lubi to: