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.

C#Technikum
CEL LEKCJI

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.

TEORIA

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.

Program.cs
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());
    }
}
Wynik przykładu
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.

Program.cs
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());
    }
}
Wynik przykładu
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ć.

TEORIA

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.

Fragment C#
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.

Fragment C#
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.

Fragment C#
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

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.

Program.cs — cały plik
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());
    }
}
Oczekiwany wynik
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.

Pobierz Program.cs

ELEMENTY WBUDOWANE

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

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

Zadania

ZAD 1Bilet★☆☆

Dodaj Bilet : IDrukowalny zwracający Bilet do muzeum.

Sprawdź: Wywołanie przez IDrukowalny zwraca napis biletu.

ZAD 2Kolejka dokumentów★★☆

Utwórz tablicę IDrukowalny z etykietą i biletem.

Sprawdź: Pętla wypisuje oba teksty.

ZAD 3Umowa ładowania★★★

Zaprojektuj interfejs ILadowalny z metodą void Laduj().

Sprawdź: Podaj dwie klasy, które mogłyby go implementować.

PODSUMOWANIE

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.