Dataczwartek, 13 sierpnia 2026 Czas18:40:10
← Programowanie konsolowe (C#)

Lekcja 15. Klasy abstrakcyjne i interfejsy

Trudny / egzaminacyjny

Po co się tego uczymy?

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.

Teoria

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) { ... } }

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.

Przykład z życia

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.

Kod (C#)

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();
        }
    }
}

Komentarz i wyjaśnienie kodu

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.

Ćwiczenie samodzielne

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

Plac zabaw — wypróbuj online

Poniższy kod wykonuje się od razu w Twojej przeglądarce — nic nie trzeba instalować. Zmień kod i kliknij „Uruchom”.

CSHARP

Zadania do pracy własnej

  1. Zdefiniuj interfejs IOpisywalny z metodą string Opisz() i zaimplementuj go w dwóch niezwiązanych ze sobą klasach (np. Ksiazka i Samochod).

  2. Zdefiniuj klasę abstrakcyjną Pracownik z metodą abstrakcyjną ObliczWynagrodzenie() i dwie klasy pochodne z różną logiką obliczania (np. na etacie vs na godziny).

  3. Zbuduj system figur geometrycznych z klasą abstrakcyjną Ksztalt (metody abstrakcyjne ObliczPole() i ObliczObwod()) i co najmniej trzema klasami pochodnymi (koło, kwadrat, prostokąt), a następnie napisz metodę statyczną znajdującą figurę o największym polu z podanej listy figur różnych typów.

Typowe błędy

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.

Nawiązanie do egzaminu zawodowego

To realizacja efektu INF.04.4.7 z podstawy programowej: "definiuje klasy pochodne" — konkretnie kryterium dotyczące klas abstrakcyjnych, wymienione wprost w oficjalnym tekście jako część wymaganej hierarchii dziedziczenia.