Interfejsy — wspólna umowa bez wspólnego przodka
Różne urządzenia potrafią drukować etykietę. Program potrzebuje tej umiejętności, a nie wiedzy o całej konstrukcji urządzenia.
Czego się dziś nauczysz
- Zadeklarujesz prosty interfejs.
- Zaimplementujesz go w klasie.
- Przekażesz obiekt przez typ interfejsu.
Przygotowanie: lekcje 01–47. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Po co ten mechanizm? Kod przed i po
Problem: organizator zna każdą konkretną klasę
Szkolne wydarzenie ma pokaz robota i quiz. Oba można rozpocząć, ale nie muszą należeć do jednej rozbudowanej rodziny klas. Najpierw organizator ma dwie przeciążone metody.
Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program
uruchamiaj w oddzielnym projekcie konsolowym.
using System;
class Robot { public string Start() { return "Robot rusza"; } }
class Quiz { public string Start() { return "Quiz startuje"; } }
class Program
{
static void Rozpocznij(Robot robot) { Console.WriteLine(robot.Start()); }
static void Rozpocznij(Quiz quiz) { Console.WriteLine(quiz.Start()); }
static void Main()
{
Rozpocznij(new Robot());
Rozpocznij(new Quiz());
}
}
Robot rusza
Quiz startuje
Po: organizator oczekuje jednej zdolności
Interfejs IUruchamialny określa: obiekt ma dostarczyć Start bez argumentów, zwracające string. Nie określa, czy obiekt ma koła, pytania albo ekran. Klient potrzebuje tylko tej operacji.
Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program
uruchamiaj w oddzielnym projekcie konsolowym.
using System;
interface IUruchamialny { string Start(); }
class Robot : IUruchamialny
{
public string Start() { return "Robot rusza"; }
}
class Quiz : IUruchamialny
{
public string Start() { return "Quiz startuje"; }
}
class Program
{
static void Rozpocznij(IUruchamialny atrakcja)
{
Console.WriteLine(atrakcja.Start());
}
static void Main()
{
Rozpocznij(new Robot());
Rozpocznij(new Quiz());
}
}
Robot rusza
Quiz startuje
1. Wersja przed zmianą
Każdy nowy typ atrakcji wymaga następnej wersji Rozpocznij. Obie obecne wersje robią dokładnie to samo.
2. Wersja po zmianie
Jedna metoda przyjmuje IUruchamialny. Każda klasa dostarcza własną implementację umowy.
3. Co konkretnie zyskaliśmy?
Dodanie Karaoke : IUruchamialny nie wymaga nowego przeciążenia Rozpocznij. Kompilator sprawdza, czy implementacja ma wymaganą sygnaturę. Kod organizatora zależy od potrzebnej umiejętności, a nie od listy konkretnych klas. Test może dostarczyć prostą własną implementację.
4. Kiedy ten mechanizm jest zbędny?
Nie każda pojedyncza klasa potrzebuje interfejsu o identycznym zestawie metod. Interfejs pomaga, kiedy istnieją wymienne implementacje albo chcesz świadomie oddzielić klienta od szczegółów. Dodatkowa warstwa ma koszt czytania i utrzymania.
Dlaczego taka sama nazwa metody nie wystarczy?
C# wymaga zadeklarowania implementacji interfejsu przez dwukropek. Sama metoda Start o tej samej nazwie w dowolnej klasie nie pozwala przekazać jej do Rozpocznij. W zwykłej, niejawnej implementacji metoda klasy jest publiczna i nie wymaga override. Console.WriteLine(string) to jedyna użyta metoda .NET: wypisuje napis zwrócony przez nasz Start i kończy wiersz.
Wiele umiejętności i właściwości
Klasa może zapisać class Robot : IUruchamialny, ILadowalny. To dwa kontrakty, nie dwie
klasy bazowe. Interfejs może wymagać właściwości, np. int Energia { get; }. Klient
interfejsu otrzymuje prawo odczytu, nawet jeśli konkretna klasa ma dodatkowo setter.
Interfejs kontra klasa abstrakcyjna
Baza abstrakcyjna może przechowywać wspólny stan instancji i przygotowywać go konstruktorem. Interfejs nie ma pól instancji ani konstruktora instancji. Nowoczesny C# dopuszcza jednak w interfejsach m.in. implementacje domyślne, człony statyczne i jawne modyfikatory dostępu. W kursie podstawowym zaczynamy od prostych publicznych kontraktów bez ciał metod.
Rozszerzenie: jawna implementacja
Zapis string IUruchamialny.Start() { return "Start"; } wewnątrz klasy to jawna
implementacja. Nie dopisujemy do niej public. Tę wersję wywołujemy przez referencję interfejsu. Może
rozdzielić dwa kontrakty z taką samą nazwą metody. Nie jest potrzebna w głównym przykładzie —
najpierw opanuj prostą implementację.
Samodzielne sprawdzenie korzyści
Dodaj Karaoke do obu programów i porównaj liczbę zmian w organizatorze. Potem napisz własny interfejs zdolności potrzebnej dwóm niepowiązanym typom. Zanim napiszesz kod, jednym zdaniem określ, czego klient potrzebuje i czego nie musi wiedzieć.
Wyjaśnienie krok po kroku
Umowa zachowania
Interfejs IDrukowalny deklaruje metodę string Drukuj(). Klasa implementująca ten prosty interfejs musi udostępnić jej implementację. Przedrostek I jest konwencją nazewniczą.
Tak wygląda to w kodzie — Definicja interfejsu — poza Main. To fragment ilustracyjny, nie cały Program.cs.
interface IDrukowalny
{
string Drukuj();
}
Umowa określa nazwę, parametry i wynik operacji. W tej podstawowej wersji nie określa jej wykonania.
Wspólne użycie
Zmienna typu IDrukowalny może wskazywać każdy obiekt implementujący umowę. Klasa może implementować wiele interfejsów. Nie oznacza to dziedziczenia po wielu klasach.
Tak wygląda to w kodzie — Klasa implementująca zdefiniowany wcześniej IDrukowalny. To fragment ilustracyjny, nie cały Program.cs.
class Etykieta : IDrukowalny
{
public string Drukuj() { return "Sprzet szkolny"; }
}
// W Main: IDrukowalny dokument = new Etykieta();
Dwukropek deklaruje implementację kontraktu. Metoda jest publiczna; w tej implementacji nie piszemy override.
Granice tej lekcji
Współczesne interfejsy mają także bardziej zaawansowane możliwości, np. implementacje domyślne. Tutaj ćwiczymy podstawowy kontrakt: co obiekt potrafi zrobić. Wybieraj małe interfejsy z metodami potrzebnymi klientowi.
Tak wygląda to w kodzie — Definicje dwóch kontraktów i jednej klasy — poza Main. To fragment ilustracyjny, nie cały Program.cs.
interface IUruchamialny { void Start(); }
interface ILadowalny { void Laduj(); }
class Robot : IUruchamialny, ILadowalny
{
public void Start() { }
public void Laduj() { }
}
Robot deklaruje dwie zdolności. Puste ciała służą tylko pokazaniu składni; działający robot potrzebuje ich implementacji. To nie dziedziczenie po dwóch klasach.
Przykład do zbadania
Wklej cały przykład do nowego projektu konsolowego jako Program.cs. Zastąp poprzednią zawartość pliku. Nie łącz kilku całych programów w jednym pliku.
using System;
interface IDrukowalny
{
string Drukuj();
}
class Etykieta : IDrukowalny
{
public string Drukuj() { return "Sprzet szkolny"; }
}
class Program
{
static void Main()
{
IDrukowalny dokument = new Etykieta();
Console.WriteLine(dokument.Drukuj());
}
}
Sprzet szkolny
Co dzieje się po kolei
- Interfejs określa nazwę i typ wyniku metody.
- Etykieta dostarcza konkretny wynik.
- Main korzysta z umowy i nie potrzebuje dodatkowych właściwości Etykiety.
Użyte w tej lekcji elementy
Metody .NET są gotowymi narzędziami biblioteki. Instrukcje języka i nasze metody oznaczamy osobno. Właściwości, np. Length, odczytujemy bez nawiasów.
| Element | Wynik | Działanie i ograniczenia |
|---|---|---|
dokument.Drukuj() |
string | Nasza metoda określona kontraktem IDrukowalny. W przykładzie tworzy tekst, nie wysyła nic do fizycznej drukarki. |
Console.WriteLine(tekst) |
void | Pokazuje przygotowany tekst. |
Częste błędy
- Nie utworzysz obiektu prostego interfejsu przez new IDrukowalny().
- W publicznej implementacji nie pomijaj public przy metodzie klasy.
- Interfejs opisuje zachowanie; nazwa nie powinna obiecywać więcej, niż deklarowane metody.
Interfejsy w bibliotece .NET
Najczęściej implementowany interfejs w kodzie szkolnym to IComparable<T>, który
mówi kolekcjom, jak porządkować twoje obiekty. Zajmuje się nim lekcja 51.
Zadania
Dodaj Bilet : IDrukowalny zwracający Bilet do muzeum.
Sprawdź: Wywołanie przez IDrukowalny zwraca napis biletu.
Utwórz tablicę IDrukowalny z etykietą i biletem.
Sprawdź: Pętla wypisuje oba teksty.
Zaprojektuj interfejs ILadowalny z metodą void Laduj().
Sprawdź: Podaj dwie klasy, które mogłyby go implementować.
Sprawdź, czy rozumiesz
Wyjaśnij własnymi słowami, jak działa główny przykład i dlaczego daje taki wynik. Następnie zmień jedną daną i przewidź wynik przed uruchomieniem. Wróć do celów lekcji i sprawdź, które potrafisz już wykonać samodzielnie.
Dokumentacja: Microsoft Learn — temat tej lekcji.