Przejdź do treści
School IT / Przestrzeń do nauki
← C# od podstawLekcja 49 z 58

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# interface implementacja IComparable is CS0535 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wyjaśnisz, dlaczego klasa bazowa nie wystarcza, gdy wspólna jest tylko jedna umiejętność.
  • Zadeklarujesz interfejs z metodą i właściwością oraz zaimplementujesz go w klasie.
  • Zaimplementujesz kilka interfejsów w jednej klasie, także obok klasy bazowej.
  • Użyjesz interfejsu jako typu zmiennej, parametru i tablicy.
  • Sprawdzisz operatorem is, czy obiekt spełnia daną umowę.
  • Rozstrzygniesz, kiedy potrzebny jest interfejs, a kiedy klasa abstrakcyjna.
  • Zaimplementujesz gotowy interfejs z biblioteki — IComparable<T>.
  • Rozpoznasz komunikaty CS0535, CS0737, CS0115, CS1721 i CS1722.
TEORIA

Jedna baza to za mało

Szkolny inwentarz drukuje etykiety z kodem. Etykietę dostaje komputer z pracowni, książka z biblioteki i legitymacja ucznia. Wszystkie trzy rzeczy trzeba wypisać jedną pętlą.

Z lekcji 45 znasz jedno narzędzie do „wspólnego typu” — klasę bazową. Spróbujmy:

tak nie zadziała
class MaEtykiete
{
    public virtual string Etykieta() { return "???"; }
}

class Komputer : Urzadzenie, MaEtykiete    // BLAD CS1721
{
}

CS1721

„Klasa Komputer nie może mieć wielu klas bazowych”. W C# klasa ma dokładnie jedną klasę bazową. Komputer jest już urządzeniem i nie może przy okazji być czymś drugim.

Nawet gdyby dziedziczenie po dwóch klasach było dozwolone, pomysł byłby zły. Spójrz na te trzy rzeczy:

RzeczCzy jest urządzeniem?Czy ma etykietę?
komputer z pracownitaktak
książka z bibliotekinietak
legitymacja ucznianietak
drukarkataktak
licznik wejśćtaknie

Nie ma wspólnego przodka, bo te rzeczy nie mają ze sobą nic wspólnego poza jedną umiejętnością. Książka nie jest urządzeniem, uczeń tym bardziej. Wspólna klasa bazowa dla nich wszystkich musiałaby się nazywać „Rzecz” i nie zawierałaby ani jednego sensownego pola.

Czego szukamy

Potrzebujemy zapisu, który mówi: „nie interesuje mnie, czym to jest — interesuje mnie, że potrafi podać etykietę”. W C# nazywa się to interfejsem. Klasa może mieć jedną bazę, ale dowolnie wiele interfejsów.

TEORIA

Umowa bez wykonania

Interfejs to spis umiejętności bez ani jednej linii wykonania. Deklaruje się go słowem interface:

definicja interfejsu
interface IEtykieta
{
    string TekstEtykiety();        // tylko nazwa, typ wyniku i parametry
    string Kod { get; }            // wlasciwosc tylko do odczytu
}

Trzy rzeczy w tym zapisie różnią się od klasy:

  • Nie ma ciał metod — po nawiasach stoi średnik. Interfejs mówi „co”, nigdy „jak”.
  • Nie ma modyfikatorów public ani abstract. Wszystkie składowe interfejsu są publiczne i abstrakcyjne z definicji, więc się tego nie pisze.
  • Nie ma pól ani konstruktora. Interfejs nie przechowuje danych, więc nie ma czego ustawiać.

Klasa implementuje interfejs — zapis jest taki sam jak dziedziczenie, po dwukropku:

definicja typu
class Ksiazka : IEtykieta
{
    public string Tytul { get; }
    public string Autor { get; }
    public string Kod { get; }              // z interfejsu

    public Ksiazka(string tytul, string autor, string kod)
    {
        Tytul = tytul;
        Autor = autor;
        Kod = kod;
    }

    public string TekstEtykiety()          // z interfejsu
    {
        return $"{Kod}  {Autor}: {Tytul}";
    }
}

Bez słowa override

To najczęstsza pomyłka po lekcji 47. Składowa z interfejsu nie jest przesłonięciem — implementujesz ją zwykłym public. Dopisanie override daje CS0115. Musi być natomiast public: bez tego słowa metoda jest prywatna i kompilator uzna, że interfejs nie został zrealizowany (CS0737).

Kompilator pilnuje kompletności tak samo jak przy klasie abstrakcyjnej:

niepełna implementacja
class Ksiazka : IEtykieta        // BLAD CS0535
{
    public string Tytul { get; }
    // nie ma ani TekstEtykiety(), ani Kod
}

CS0535 — „Ksiazka nie implementuje elementu członkowskiego interfejsu IEtykieta.TekstEtykiety()”. Umowa albo jest dotrzymana w całości, albo program się nie buduje.

Interfejs jako typ zmiennej

Tak jak klasa abstrakcyjna, interfejs nie da się utworzyć przez new, ale świetnie nadaje się na typ zmiennej, parametru i tablicy:

Program.cs
IEtykieta e = new Ksiazka("Pan Tadeusz", "Mickiewicz", "KS-102");
Console.WriteLine(e.TekstEtykiety());

// IEtykieta x = new IEtykieta();       // BLAD CS0144 - interfejsu nie da sie utworzyc

static void Wydrukuj(IEtykieta co)      // dziala dla KAZDEJ klasy z ta umowa
{
    Console.WriteLine($"[ {co.TekstEtykiety()} ]");
}

Metoda Wydrukuj nie wie i nie chce wiedzieć, czy dostała książkę, komputer czy legitymację. Wie tylko jedno: to coś potrafi podać tekst etykiety. Na tym polega cała siła interfejsów.

Skąd litera I

Nazwy interfejsów w C# zaczyna się wielką literą I: IEtykieta, IComparable, IDisposable. To nie jest wymóg kompilatora, tylko konwencja z lekcji 05 — pozwala na pierwszy rzut oka odróżnić interfejs od klasy. Cała biblioteka .NET jest tak nazwana, więc trzymaj się tego.

TEORIA

Jedna baza, dowolnie wiele umów

Tu jest różnica, dla której interfejsy istnieją: klasa może mieć jedną bazę i dowolnie wiele interfejsów.

jedna baza, dwa interfejsy
interface IEtykieta
{
    string TekstEtykiety();
    string Kod { get; }
}

interface ISerwisowane
{
    int DniDoSerwisu { get; }
    void Serwisuj();
}

class Komputer : Urzadzenie, IEtykieta, ISerwisowane
{
    // ...
}

Kolejność po dwukropku jest ustalona: najpierw klasa bazowa, potem interfejsy. Odwrotnie daje CS1722. Same interfejsy można wymieniać w dowolnej kolejności.

ZapisPoprawny?Uwaga
class A : Bazataktylko dziedziczenie
class A : IJednotaktylko interfejs, baza domyślnie object
class A : Baza, IJedno, IDrugietakto jest typowy zapis
class A : IJedno, BazanieCS1722 — klasa bazowa musi być pierwsza
class A : Baza1, Baza2nieCS1721 — jedna klasa bazowa
interface I : IJedno, IDrugietakinterfejs może dziedziczyć po wielu interfejsach

Ta sama umowa w niepowiązanych klasach

Teraz trzy klasy z zupełnie różnych światów trafiają do jednej tablicy:

Program.cs
IEtykieta[] doWydruku =
{
    new Komputer("Dell OptiPlex", "KOM-014"),
    new Ksiazka("Pan Tadeusz", "Mickiewicz", "KS-102"),
    new Legitymacja("Kasia Nowak", "3TP", "LEG-88")
};

foreach (IEtykieta e in doWydruku)
{
    Console.WriteLine(e.TekstEtykiety());
}

Komputer dziedziczy po Urzadzenie, Ksiazka po niczym, Legitymacja po niczym. Nie mają wspólnego przodka poza object — a mimo to mieszczą się w jednej tablicy, bo mają wspólną umowę.

Sprawdzanie, czy klasa spełnia umowę

is i as z lekcji 46 działają także z interfejsami:

Program.cs
foreach (object rzecz in inwentarz)
{
    if (rzecz is IEtykieta e)
    {
        Console.WriteLine(e.TekstEtykiety());
    }

    if (rzecz is ISerwisowane s && s.DniDoSerwisu < 30)
    {
        Console.WriteLine($"  UWAGA: serwis za {s.DniDoSerwisu} dni");
    }
}

Jeden obiekt może pasować do kilku sprawdzeń naraz — komputer wejdzie do obu if, a książka tylko do pierwszego.

Gdy dwa interfejsy mają metodę o tej samej nazwie

Zdarza się, że dwa interfejsy wymagają metody Zamknij(), a klasa musi zrobić w każdym przypadku coś innego. Wtedy pisze się implementację jawną — bez public, z nazwą interfejsu przed nazwą metody:

void IPlik.Zamknij() { ... } i void IOkno.Zamknij() { ... }

Taka metoda jest dostępna tylko przez zmienną odpowiedniego interfejsu, nie przez zmienną klasy. W zadaniach szkolnych rzadko tego potrzebujesz — wystarczy, że wiesz, że taki zapis istnieje i co znaczy.

TEORIA

Sześć różnic i jedna decyzja

Interfejs i klasa abstrakcyjna z lekcji 47 wyglądają podobnie: obie zapowiadają składowe bez ciała i obu nie da się utworzyć. Różnic jest jednak sześć i wszystkie są istotne.

Klasa abstrakcyjnaInterfejs
Ile na klasęjednadowolnie wiele
Polataknie
Konstruktortaknie
Gotowy kod metodtak, dowolnie dużotylko domyślne implementacje, i to rzadko
Składowe niepublicznetak (protected, private)nie — wszystko publiczne
Słowo w klasie pochodnejoverridepublic, bez override
Odpowiada na pytanieczym to jestco to potrafi
Typowa nazwarzeczownik: Bilet, Figuraprzymiotnik lub umiejętność: IPorownywalny, IEtykieta

Jak wybrać

SytuacjaWybór
Klasy mają wspólne pola i wspólny kodklasa abstrakcyjna
Klasy nie mają nic wspólnego poza jedną umiejętnościąinterfejs
Klasa już dziedziczy po czymś innyminterfejs — bazy nie da się dołożyć
Potrzebny licznik, numerowanie albo wspólny konstruktorklasa abstrakcyjna
Chcesz opisać umiejętność, którą mogą mieć różne hierarchieinterfejs
Jedno i drugieoba naraz — klasa abstrakcyjna, która implementuje interfejs

Bardzo częste rozwiązanie

Klasa abstrakcyjna i interfejs nie są konkurencją. W bibliotece .NET stale spotkasz zapis abstract class Strumien : IDisposable: interfejs mówi, co obiekt potrafi, a klasa abstrakcyjna dostarcza gotowe pola i połowę kodu dla swoich potomków.

TEORIA

Umowy, które ktoś już napisał

Interfejsów nie pisze się głównie samemu — częściej implementuje się gotowe z biblioteki. Cztery spotkasz najszybciej.

IComparable<T> — „umiem się porównać”

Klasa, która ma jedną metodę CompareTo, zaczyna działać z Array.Sort z lekcji 24:

definicja typu
class Uczen : IComparable<Uczen>
{
    public string Nazwisko { get; }
    public int Punkty { get; }

    public Uczen(string nazwisko, int punkty)
    {
        Nazwisko = nazwisko;
        Punkty = punkty;
    }

    public int CompareTo(Uczen? inny)
    {
        if (inny == null)
        {
            return 1;
        }
        return inny.Punkty - Punkty;      // malejaco po punktach
    }

    public override string ToString()
    {
        return $"{Nazwisko} {Punkty} pkt";
    }
}
Program.cs
Uczen[] wyniki =
{
    new Uczen("Nowak", 34),
    new Uczen("Lis", 51),
    new Uczen("Wrona", 42)
};

Array.Sort(wyniki);                   // dziala, bo Uczen umie sie porownac

foreach (Uczen u in wyniki)
{
    Console.WriteLine(u);
}
wynik w konsoli
Lis 51 pkt
Wrona 42 pkt
Nowak 34 pkt

Array.Sort został napisany wiele lat przed Twoją klasą i nic o niej nie wie. Potrafi ją posortować, bo wymaga tylko jednej umowy: IComparable<T>. Do tego wracamy w lekcji 51.

IEnumerable<T> — „można mnie przejść pętlą foreach”

To umowa, którą spełniają tablice, List<T> i wszystkie kolekcje z lekcji 31–33. Właśnie dlatego foreach działa z każdą z nich, choć w środku są zupełnie różne.

IDisposable — „mam co zwolnić”

Znasz ją z lekcji 36 i 40: klasa z metodą Dispose() daje się użyć w bloku using, który wywoła zwolnienie także wtedy, gdy poleci wyjątek.

IFormattable, IEquatable<T> i pozostałe

Nie trzeba ich pamiętać. Trzeba pamiętać schemat: gdy jakaś gotowa metoda w .NET „nie chce” Twojej klasy, poszukaj w dokumentacji, jakiego interfejsu wymaga jej parametr. Prawie zawsze wystarczy dopisać jedną metodę.

PRZYKŁAD

Inwentarz szkolny

Inwentarz szkolny: dwa interfejsy, jedna klasa abstrakcyjna i cztery klasy, które łączą się w różne kombinacje. Zwróć uwagę, że tablica jest typu object[] — nie ma typu, który obejmowałby wszystkie te rzeczy.

Program.cs — pełny plik
// Program.cs - pelny plik
object[] inwentarz =
{
    new Komputer("Dell OptiPlex", "KOM-014", 12),
    new Komputer("HP ProDesk", "KOM-015", 95),
    new Ksiazka("Pan Tadeusz", "Mickiewicz", "KS-102"),
    new Legitymacja("Kasia Nowak", "3TP", "LEG-88"),
    new LicznikWejsc()
};

Console.WriteLine("=== etykiety do wydruku ===");
foreach (object rzecz in inwentarz)
{
    if (rzecz is IEtykieta e)
    {
        Console.WriteLine(e.TekstEtykiety());
    }
}

Console.WriteLine();
Console.WriteLine("=== plan serwisu ===");
foreach (object rzecz in inwentarz)
{
    if (rzecz is ISerwisowane s)
    {
        if (s.DniDoSerwisu < 30)
        {
            Console.Write("PILNE: ");
        }
        s.Serwisuj();
    }
}

Console.WriteLine();
Console.WriteLine("=== jedna metoda dla wszystkiego, co ma etykiete ===");
Wydrukuj(new Ksiazka("Lalka", "Prus", "KS-201"));
Wydrukuj(new Legitymacja("Marek Lis", "2TP", "LEG-91"));

Console.WriteLine();
Console.WriteLine("=== co spelnia ktora umowe ===");
foreach (object rzecz in inwentarz)
{
    string nazwa = rzecz.GetType().Name;
    string etyk = rzecz is IEtykieta ? "IEtykieta" : "-";
    string serw = rzecz is ISerwisowane ? "ISerwisowane" : "-";
    Console.WriteLine($"{nazwa,-14} {etyk,-11} {serw}");
}

static void Wydrukuj(IEtykieta co)
{
    Console.WriteLine($"+-- {co.Kod} " + new string('-', 20));
    Console.WriteLine($"|   {co.TekstEtykiety()}");
    Console.WriteLine("+" + new string('-', 27));
}


interface IEtykieta
{
    string Kod { get; }
    string TekstEtykiety();
}

interface ISerwisowane
{
    int DniDoSerwisu { get; }
    void Serwisuj();
}

abstract class Urzadzenie
{
    public string Nazwa { get; }

    protected Urzadzenie(string nazwa)
    {
        Nazwa = nazwa;
    }

    public abstract string Opis();
}

class Komputer : Urzadzenie, IEtykieta, ISerwisowane
{
    public string Kod { get; }
    public int DniDoSerwisu { get; }

    public Komputer(string nazwa, string kod, int dniDoSerwisu) : base(nazwa)
    {
        Kod = kod;
        DniDoSerwisu = dniDoSerwisu;
    }

    public override string Opis()
    {
        return $"komputer {Nazwa}";
    }

    public string TekstEtykiety()
    {
        return $"{Kod}  {Nazwa}  (pracownia)";
    }

    public void Serwisuj()
    {
        Console.WriteLine($"{Kod}: przeglad za {DniDoSerwisu} dni");
    }
}

class LicznikWejsc : Urzadzenie, ISerwisowane
{
    public int DniDoSerwisu => 200;

    public LicznikWejsc() : base("licznik wejsc") { }

    public override string Opis()
    {
        return "licznik wejsc przy drzwiach";
    }

    public void Serwisuj()
    {
        Console.WriteLine($"licznik: przeglad za {DniDoSerwisu} dni");
    }
}

class Ksiazka : IEtykieta
{
    public string Tytul { get; }
    public string Autor { get; }
    public string Kod { get; }

    public Ksiazka(string tytul, string autor, string kod)
    {
        Tytul = tytul;
        Autor = autor;
        Kod = kod;
    }

    public string TekstEtykiety()
    {
        return $"{Kod}  {Autor}: {Tytul}";
    }
}

class Legitymacja : IEtykieta
{
    public string Uczen { get; }
    public string Klasa { get; }
    public string Kod { get; }

    public Legitymacja(string uczen, string klasa, string kod)
    {
        Uczen = uczen;
        Klasa = klasa;
        Kod = kod;
    }

    public string TekstEtykiety()
    {
        return $"{Kod}  {Uczen} / {Klasa}";
    }
}
wynik w konsoli
=== etykiety do wydruku ===
KOM-014  Dell OptiPlex  (pracownia)
KOM-015  HP ProDesk  (pracownia)
KS-102  Mickiewicz: Pan Tadeusz
LEG-88  Kasia Nowak / 3TP

=== plan serwisu ===
PILNE: KOM-014: przeglad za 12 dni
KOM-015: przeglad za 95 dni
licznik: przeglad za 200 dni

=== jedna metoda dla wszystkiego, co ma etykiete ===
+-- KS-201 --------------------
|   KS-201  Prus: Lalka
+---------------------------
+-- LEG-91 --------------------
|   LEG-91  Marek Lis / 2TP
+---------------------------

=== co spelnia ktora umowe ===
Komputer       IEtykieta   ISerwisowane
Komputer       IEtykieta   ISerwisowane
Ksiazka        IEtykieta   -
Legitymacja    IEtykieta   -
LicznikWejsc   -           ISerwisowane

Co dzieje się po kolei

  • Komputer ma klasę bazową i dwa interfejsy — dokładnie to, czego nie dałoby się zrobić samym dziedziczeniem.
  • Ksiazka i Legitymacja nie dziedziczą po niczym, a mimo to trafiają do tej samej pętli co komputer, bo spełniają tę samą umowę.
  • LicznikWejsc jest urządzeniem i wymaga serwisu, ale nie ma etykiety — dlatego nie pojawia się w pierwszej pętli. Interfejsy dobiera się osobno dla każdej klasy.
  • Pierwsza i druga pętla przechodzą tę samą tablicę, ale wybierają z niej różne podzbiory. Filtrem jest interfejs, nie klasa.
  • Wydrukuj ma parametr typu IEtykieta. Nie zna klasy Ksiazka ani Legitymacja — używa tylko Kod i TekstEtykiety(), bo tyle gwarantuje umowa.
  • Ostatnia pętla pokazuje pełny obraz: każda klasa spełnia inny zestaw umów, a is odpowiada na pytanie „czy potrafi”, nie „czym jest”.
  • DniDoSerwisu w klasie Komputer jest zwykłą właściwością z konstruktora, a w LicznikWejsc właściwością wyliczaną. Interfejs nie narzuca sposobu — tylko to, że wartość ma być dostępna do odczytu.

Sprawdź trzy rzeczy

  1. Usuń public przed TekstEtykiety w klasie Ksiazka. Dostaniesz CS0737 — składowa implementująca interfejs musi być publiczna.
  2. Dopisz override przed TekstEtykiety. Dostaniesz CS0115: to nie jest przesłonięcie, bo w żadnej klasie bazowej nie ma takiej metody.
  3. Dopisz do Ksiazka interfejs ISerwisowane, ale nic więcej. CS0535 wymieni obie brakujące składowe. Potem dopisz je i sprawdź, że książka pojawiła się w planie serwisu, choć nie zmieniłeś ani jednej linii w pętlach.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
interface IX { }deklaracja umowyBez pól, bez konstruktora, bez ciał metod.
składowa interfejsunazwa, typ i parametryKończy się średnikiem. Domyślnie publiczna i abstrakcyjna.
class A : IXklasa realizuje umowęMusi podać wszystkie składowe (CS0535).
public w klasieimplementacja składowejBez override. Brak public daje CS0737.
class A : Baza, IX, IYjedna baza i wiele interfejsówKlasa bazowa zawsze pierwsza (CS1722).
interface IX : IYinterfejs rozszerza innyTu dziedziczenie wielokrotne jest dozwolone.
IX zmiennatyp zmiennej lub parametruWidać tylko składowe z umowy.
rzecz is IX xsprawdzenie, czy klasa spełnia umowęJeden obiekt może pasować do kilku interfejsów.
void IX.Metoda()implementacja jawnaDla dwóch umów z tą samą nazwą metody. Bez public.
IComparable<T>„umiem się porównać”Metoda CompareTo włącza Array.Sort.
IEnumerable<T>„można mnie przejść pętlą”Spełniają ją tablice i wszystkie kolekcje.
IDisposable„mam co zwolnić”Metoda Dispose() i blok using.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
override przy składowej z interfejsuCS0115. Interfejs nie ma ciała do przesłonięcia — pisz samo public.
Brak public przy implementacjiCS0737. Metoda jest prywatna, więc umowa nie jest spełniona.
Brakująca składowa interfejsuCS0535. Kompilator wypisze dokładną nazwę i sygnaturę — przeczytaj komunikat do końca.
Inna sygnatura niż w interfejsieCS0535, bo to jest inna metoda. Zwróć uwagę na typ wyniku i na liczbę parametrów.
Interfejs przed klasą bazową po dwukropkuCS1722. Kolejność: najpierw baza, potem interfejsy.
Dwie klasy bazoweCS1721. To właśnie po to są interfejsy.
Pole w interfejsieCS0525. Interfejs nie przechowuje danych — użyj właściwości.
Konstruktor w interfejsieCS0526. Interfejsu nie tworzy się przez new.
new IEtykieta()CS0144. Utwórz obiekt klasy, która tę umowę spełnia.
Interfejs z dziesięcioma metodamiUmowa, której nikt nie chce podpisać. Lepiej dwa lub trzy małe interfejsy niż jeden wielki.
Interfejs zamiast klasy abstrakcyjnej dla wspólnych pólSkoro klasy mają wspólne dane, potrzebna jest klasa bazowa — interfejs nie przechowuje pól.
ZADANIA

Zadania

ZAD 1Pierwszy własny interfejs★☆☆

Napisz interfejs IOpisywalny z jedną metodą string Opis(). Zaimplementuj go w dwóch niepowiązanych klasach: Ksiazka (tytuł i autor) oraz Rower (marka i liczba biegów). W programie utwórz tablicę IOpisywalny[] z trzema obiektami — dwiema książkami i jednym rowerem — i wypisz wszystkie opisy jedną pętlą foreach. Następnie usuń metodę Opis z klasy Rower, spróbuj skompilować i zapisz w komentarzu numer błędu.

ZAD 2Właściwość w interfejsie i metoda z parametrem★☆☆

Rozbuduj interfejs IOpisywalny z zadania 1 o właściwość string Kod { get; }. Uzupełnij obie klasy. Dopisz metodę static void Etykieta(IOpisywalny co), która wypisuje w ramce kod i opis, i wywołaj ją dla każdego elementu tablicy. Sprawdź, co się stanie, gdy w klasie Ksiazka usuniesz słowo public przed metodą Opis — zapisz numer błędu w komentarzu.

ZAD 3Dwa interfejsy i klasa bazowa★★☆

Napisz klasę abstrakcyjną Sprzet z właściwością Nazwa i abstrakcyjną metodą string Kategoria(). Napisz dwa interfejsy: IWypozyczalny (właściwość bool Dostepny { get; } i metoda void Wypozycz(string komu)) oraz INaprawialny (metoda void Napraw()). Napisz trzy klasy: Laptop dziedziczący po Sprzet i implementujący oba interfejsy, Projektor dziedziczący po Sprzet i implementujący tylko INaprawialny, oraz Ksiazka, która nie dziedziczy po niczym i implementuje tylko IWypozyczalny. Utwórz tablicę object[] ze wszystkimi obiektami i napisz dwie pętle: pierwsza wypożycza wszystko, co da się wypożyczyć, druga naprawia wszystko, co da się naprawić. Użyj is ze zmienną.

ZAD 4Wybór narzędzia★★☆

Dla każdej z sześciu sytuacji zapisz w komentarzu, czy użyjesz klasy abstrakcyjnej, interfejsu, czy obu, i uzasadnij jednym zdaniem: (a) wszystkie figury geometryczne mają nazwę i liczą pole; (b) niektóre obiekty w grze można zapisać do pliku — są to zarówno postacie, jak i mapy; (c) wszyscy pracownicy mają imię, nazwisko i numer, a pensję liczą inaczej; (d) niektóre obiekty da się posortować; (e) wszystkie bilety numerują się automatycznie i mają cenę zależną od rodzaju; (f) zarówno drukarka, jak i plik dziennika mają metodę zamykania. Następnie zaimplementuj w kodzie punkty (b) i (e): dla (b) napisz interfejs IZapisywalny z metodą string DoTekstu() i dwie niepowiązane klasy, które go spełniają; dla (e) napisz klasę abstrakcyjną z licznikiem i dwie klasy pochodne. Uruchom oba przykłady.

ZAD 5Własny IComparable★★☆

Napisz klasę Zawodnik z właściwościami Nazwisko, Czas (w sekundach, typ double) i Punkty. Zaimplementuj IComparable<Zawodnik> tak, żeby sortowanie ustawiało zawodników od najkrótszego czasu. Utwórz tablicę pięciu zawodników, posortuj ją metodą Array.Sort i wypisz wyniki z numerami miejsc. Przesłoń ToString(), żeby wypisanie zawodnika dawało czytelny wiersz. Na koniec zmień CompareTo tak, żeby sortowało malejąco po punktach, uruchom ponownie i zapisz w komentarzu, ile linii programu poza samą metodą CompareTo musiałeś zmienić.

ZAD 6System wypożyczeń — pełny program★★★

Zbuduj program obsługujący szkolną wypożyczalnię. Napisz interfejsy: IEtykieta (właściwość Kod { get; }, metoda string TekstEtykiety()), IWypozyczalny (właściwość bool Dostepny { get; }, metody bool Wypozycz(string komu) i void Zwroc()) oraz IPlatny (właściwość decimal OplataZaDzien { get; }). Napisz klasę abstrakcyjną PozycjaInwentarza z automatycznym numerem z licznika statycznego, właściwością Nazwa i abstrakcyjną metodą string Kategoria(); niech ta klasa implementuje IEtykieta, budując kod z kategorii i numeru. Napisz trzy klasy pochodne: Laptop (wypożyczalny, płatny 5 zł za dzień), Ksiazka (wypożyczalna, darmowa) i TablicaInteraktywna (nie wypożyczana — implementuje tylko to, co daje klasa bazowa). W programie utwórz listę co najmniej sześciu pozycji, a następnie: wypisz wszystkie etykiety; wypożycz trzy pozycje różnym osobom i spróbuj wypożyczyć jedną z nich po raz drugi, obsługując odmowę; wypisz tabelkę pozycji z kolumnami numer, kategoria, nazwa, dostępność i opłata dzienna (dla darmowych wpisz kreskę); policz łączny dzienny przychód z aktualnie wypożyczonych pozycji płatnych. Na końcu pliku, w komentarzu, odpowiedz: dlaczego IEtykieta została zaimplementowana w klasie abstrakcyjnej, a nie w każdej klasie pochodnej osobno, oraz co trzeba zmienić w programie, żeby dodać pozycję „mikroskop”, wypożyczalną i płatną 2 zł za dzień.

PODSUMOWANIE

Co trzeba zapamiętać

  • Interfejs to umowa: spis umiejętności bez ani jednej linii wykonania.
  • Klasa ma jedną klasę bazową, ale dowolnie wiele interfejsów — to główna różnica.
  • Po dwukropku najpierw klasa bazowa, potem interfejsy (inaczej CS1722).
  • Składowe interfejsu są publiczne i abstrakcyjne z definicji, więc modyfikatorów się nie pisze.
  • W klasie implementującej używasz public, nie override.
  • Brak choćby jednej składowej to błąd CS0535 — umowa albo jest spełniona w całości, albo program się nie buduje.
  • Interfejsu nie da się utworzyć przez new, ale jest doskonałym typem zmiennej i parametru.
  • Interfejs odpowiada na pytanie „co to potrafi”, klasa abstrakcyjna na „czym to jest”.
  • Interfejs nie ma pól ani konstruktora — gdy klasy mają wspólne dane, potrzebna jest klasa bazowa.
  • Klasa abstrakcyjna i interfejs często występują razem: umowa na zewnątrz, wspólny kod w bazie.
  • Nazwy interfejsów zaczynają się od I — to konwencja całej biblioteki .NET.
  • Gotowe metody .NET współpracują z Twoimi klasami dzięki interfejsom: IComparable<T>, IEnumerable<T>, IDisposable.

Dokumentacja: Microsoft Learn — temat tej lekcji.

Postęp zapisuje się w tej przeglądarce.