INF.04 · C# · lekcja 15

Klasy abstrakcyjne i interfejsy

Oddziel wymaganie od implementacji

  • Zrozum
  • Sprawdź
  • Napisz sam

Po tej lekcji: oddziel wymaganie od implementacji

Dzisiaj zrobisz konkretną rzecz

Klasy abstrakcyjne i interfejsy to narzędzia PROJEKTOWANIA dobrego kodu — wymuszają na programistach (także na Tobie samym za kilka miesięcy) konsekwentne implementowanie wymaganych zachowań, zamiast polegać na pamięci czy dokumentacji.

Umowa o pracę na stanowisku "kierowca" wymaga posiadania prawa jazdy, niezależnie od tego, czy ktoś jest kierowcą autobusu, ciężarówki czy taksówki — to jak interfejs: opisuje wymaganą "umiejętność", nie mówiąc, JAK dokładnie ma być zrealizowana.

Sprawdź w działaniu

Oddziel wymaganie od implementacji

Najpierw przewidź wynik. Zmień dane w modelu i porównaj efekt. Spróbuj także przypadku granicznego, a nie tylko pierwszego poprawnego przykładu.

Ta interakcja wymaga włączonego JavaScriptu.

Model pokazuje wybraną zasadę. Nie uruchamia ani nie ocenia wklejonego kodu C#.

1. Przygotuj i uruchom własny projekt

2. Najpierw mały przykład

Wykonaj tę próbę przed dłuższym rozwiązaniem. interface opisuje wymagane operacje. Film : IOpis zobowiązuje klasę do udostępnienia publicznej metody o wskazanej sygnaturze. Klasa abstrakcyjna może dodatkowo przechowywać wspólny stan i implementację.

Program.cscsharp
IOpis obiekt = new Film();
Console.WriteLine(obiekt.Opisz());

interface IOpis { string Opisz(); }
class Film : IOpis
{
    public string Opisz() => "Film do wypożyczenia";
}
Wynik
Film do wypożyczenia
  1. Co sprawdzić? Zmień jedną wartość i przed uruchomieniem zapisz przewidywany rezultat. Porównaj go z opisem wyniku; przy błędzie wróć do konkretnej instrukcji.

Jak to działa — 1

W Lekcji 14 klasa Pracownik miała sensowną, domyślną wersję ObliczPremie() (5%). Czasem jednak klasa bazowa reprezentuje pojęcie zbyt OGÓLNE, żeby mieć jakąkolwiek sensowną implementację metody — wtedy używa się klasy abstrakcyjnej.

Klasa abstrakcyjna (abstract class) NIE MOŻE być bezpośrednio "zmaterializowana" przez new — służy WYŁĄCZNIE jako baza do dziedziczenia. Może zawierać zarówno zwykłe metody (ze wspólną implementacją), jak i metody abstrakcyjne (abstract) — bez ciała, WYMUSZAJĄCE, żeby każda klasa pochodna sama je zaimplementowała: abstract class Ksztalt { public abstract double ObliczPole(); } Klasa Ksztalt nie wie, jak obliczyć pole — to zależy od konkretnego kształtu, więc każda klasa pochodna (Kolo, Kwadrat) MUSI dostarczyć własną implementację ObliczPole(), inaczej program się nie skompiluje.

Interfejs (interface) idzie o krok dalej — to CZYSTA umowa, opisująca, JAKIE metody musi mieć klasa, całkowicie bez żadnej implementacji. Klasa może zaimplementować DOWOLNIE WIELE interfejsów (w przeciwieństwie do dziedziczenia z jednej klasy bazowej): interface IPorownywalny { int Porownaj(object inny); } class Produkt : IPorownywalny { public int Porownaj(object inny) { ... } }

Jak to działa — 2

Kiedy klasa abstrakcyjna, a kiedy interfejs? Klasa abstrakcyjna, gdy klasy pochodne mają WSPÓLNĄ implementację części zachowania (np. wszystkie kształty mają pole Nazwa). Interfejs, gdy chcesz tylko WYMUSIĆ, że dana klasa "potrafi" coś zrobić, niezależnie od tego, z czego dziedziczy (np. "da się porównać", "da się zapisać do pliku") — klasa może "potrafić" wiele różnych rzeczy naraz.

3. Pełny przykład — czytaj od Main

Teraz zastąp Program.cs tym programem. Zanim uruchomisz, znajdź dane wejściowe, operację i miejsce prezentacji wyniku. W tym wariancie zachowujemy jawny Main. Nie wklejaj kilku pełnych programów do jednego pliku.

Program.cs — osobne uruchomieniecsharp
using System;

abstract class Ksztalt
{
    public string Nazwa;

    public Ksztalt(string nazwa)
    {
        Nazwa = nazwa;
    }

    public abstract double ObliczPole(); // BEZ implementacji - wymusza nadpisanie

    public void WyswietlPole() // zwykła metoda, wspólna dla wszystkich kształtów
    {
        Console.WriteLine(Nazwa + ": pole = " + ObliczPole());
    }
}

class Kolo : Ksztalt
{
    public double Promien;

    public Kolo(double promien) : base("Koło")
    {
        Promien = promien;
    }

    public override double ObliczPole()
    {
        return Math.PI * Promien * Promien;
    }
}

class Kwadrat : Ksztalt
{
    public double Bok;

    public Kwadrat(double bok) : base("Kwadrat")
    {
        Bok = bok;
    }

    public override double ObliczPole()
    {
        return Bok * Bok;
    }
}

class Program
{
    static void Main()
    {
        // Ksztalt ksztalt = new Ksztalt("x"); // BŁĄD - nie można utworzyć klasy abstrakcyjnej

        Ksztalt[] figury = { new Kolo(3), new Kwadrat(4) };

        foreach (Ksztalt f in figury)
        {
            f.WyswietlPole();
        }
    }
}
  1. Prześledź kod Próba new Ksztalt("x") zgłosiłaby błąd kompilacji — klasa abstrakcyjna istnieje TYLKO po to, żeby po niej dziedziczyć, nigdy żeby ją bezpośrednio tworzyć. Metoda WyswietlPole() jest zwykłą (nie-abstrakcyjną) metodą klasy Ksztalt i działa identycznie dla wszystkich kształtów, WEWNĄTRZ wywołując ObliczPole() — dzięki polimorfizmowi (Lekcja 14) automatycznie uruchomi się właściwa, nadpisana wersja dla danego typu.

Sprawdź, zanim przepiszesz cały program

Częsty błąd to próba utworzenia obiektu klasy abstrakcyjnej przez new — zawsze zakończy się błędem kompilacji. Drugi błąd: zapomnienie implementacji którejś z metod abstrakcyjnych w klasie pochodnej — kod się nie skompiluje, dopóki WSZYSTKIE metody abstrakcyjne nie zostaną nadpisane. Trzeci: mylenie interfejsu z klasą abstrakcyjną — interfejs nigdy nie zawiera żadnej implementacji (tylko "nagłówki" metod), a klasa abstrakcyjna MOŻE mieszać metody z implementacją i bez niej.

Ćwiczenie z pomocą · samodzielna praca

4. Teraz Twoja zmiana

Dodaj do przykładu z lekcji trzecią klasę pochodną Trojkat (pola: podstawa, wysokość) implementującą własną wersję ObliczPole().

Oczekiwany rezultat
Spełnij warunki polecenia. Zapisz wejście, otrzymany wynik i wyjaśnij, która część kodu za niego odpowiada.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Wróć do podstawowej formy oraz objaśnienia: interface opisuje wymagane operacje. Film : IOpis zobowiązuje klasę do udostępnienia publicznej metody o wskazanej sygnaturze. Klasa abstrakcyjna może dodatkowo przechowywać wspólny stan i implementację.

Krok 2 — pokaż podpowiedź

Zmień tylko część potrzebną do polecenia. Zachowaj działającą wersję przed zmianą i uruchamiaj po każdym małym etapie.

Krok 3 — pokaż podpowiedź

Sprawdź przypadek zwykły i graniczny; porównaj z regułą: Film do wypożyczenia

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

5. Sprawdź rozumienie, nie tylko uruchomienie

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka