Klasy abstrakcyjne – szablony, które wymuszają implementację

Nauczysz się tworzyć klasy, których nie można bezpośrednio użyć, ale które definiują „kontrakt” dla klas pochodnych. To kolejny poziom programowania obiektowego!

abstract klasa bazowa metody abstrakcyjne wymuszenie implementacji polimorfizm
01

Problem – co jeśli ktoś utworzy obiekt klasy bazowej?

W poprzednim materiale o dziedziczeniu stworzyliśmy klasę Postac jako bazową dla Wojownika, Maga i Łucznika. Ale jest pewien problem…

cs/Problem.cs C#
public class Postac
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    public virtual void Atakuj()
    {
        Console.WriteLine("Atakuję jakoś...");  // 🤔 Jak? Czym?
    }
}

// Klasy pochodne mają sens:
Wojownik conan = new Wojownik();   // ✅ OK - wojownik atakuje mieczem
Mag gandalf = new Mag();           // ✅ OK - mag rzuca czary

// Ale co z tym?
Postac ktos = new Postac();         // 🤔 Kim jest "Postac"?
ktos.Atakuj();                       // "Atakuję jakoś..." - bez sensu!
Co jest nie tak?
  • Postac to koncept abstrakcyjny – nie istnieje „po prostu postać”, istnieją wojownicy, magowie, łucznicy
  • Metoda Atakuj() w klasie bazowej jest sztuczna – musieliśmy coś napisać, ale „atakuję jakoś” nie ma sensu
  • Nic nie zmusza programisty do nadpisania Atakuj() – może zapomnieć i zostanie domyślna wersja
  • Można utworzyć new Postac() – a nie powinno to być możliwe!
Analogia: Formularz z obowiązkowymi polami

Wyobraź sobie formularz online:

  • Zwykła klasa (virtual) = pole z domyślną wartością „wpisz coś” – możesz je zostawić
  • Klasa abstrakcyjna (abstract) = pole oznaczone gwiazdką (*) – MUSISZ je wypełnić, inaczej formularz się nie wyśle

Klasa abstrakcyjna wymusza na programiście zaimplementowanie pewnych rzeczy!

Problem
new Postac() ← można utworzyć!
Atakuj() ← „jakoś…”
Można zapomnieć override
Rozwiązanie
abstract class Postac
← nie można utworzyć!
abstract void Atakuj()
← MUSISZ nadpisać!
02

Co to jest klasa abstrakcyjna?

Klasa abstrakcyjna to klasa, której nie można bezpośrednio utworzyć (nie można użyć new). Służy jako szablon dla klas pochodnych i może zawierać metody, które klasy pochodne muszą zaimplementować.

Analogia: Przepis vs danie

Pomyśl o klasie abstrakcyjnej jak o przepisie kulinarnym:

  • Przepis (klasa abstrakcyjna) = instrukcja, ale nie możesz jej zjeść
  • Spaghetti bolognese (klasa konkretna) = rzeczywiste danie zrobione według przepisu

Przepis definiuje „jak gotować”, ale musisz go zrealizować jako konkretne danie!

Analogia: Schemat budynku

Inna analogia:

  • Klasa abstrakcyjna = projekt architektoniczny budynku
  • Klasa konkretna = rzeczywisty budynek zbudowany według projektu

Nie możesz zamieszkać w projekcie – musisz najpierw zbudować dom!

Dwie kluczowe cechy

CechaCo to znaczy?Słowo kluczowe
Nie można utworzyć instancji new KlasaAbstrakcyjna() = błąd kompilacji abstract class
Może wymuszać implementację Klasy pochodne MUSZĄ nadpisać metody abstrakcyjne abstract void Metoda();
abstract class Postac
🚫 new Postac() – BŁĄD!
────────────────
Imie, PunktyZycia
abstract void Atakuj();
↓ dziedziczą i MUSZĄ zaimplementować Atakuj()
class Wojownik
✅ new Wojownik() – OK
override Atakuj() ✓
class Mag
✅ new Mag() – OK
override Atakuj() ✓
class Lucznik
✅ new Lucznik() – OK
override Atakuj() ✓
03

Podstawowa składnia

Aby oznaczyć klasę jako abstrakcyjną, dodajemy słowo kluczowe abstract przed class.

cs/SkladniaAbstract.cs C#
// Klasa abstrakcyjna – nie można utworzyć jej instancji
public abstract class Postac
{
    // Zwykłe pola i właściwości – działają normalnie
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    // Zwykła metoda – działa normalnie, dziedziczona przez klasy pochodne
    public void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Imie}, mam {PunktyZycia} HP");
    }
    
    // Metoda abstrakcyjna – BEZ ciała! Klasy pochodne MUSZĄ ją zaimplementować
    public abstract void Atakuj();  // Zwróć uwagę: średnik zamiast { }
}

// Klasa konkretna – dziedziczy i MUSI zaimplementować Atakuj()
public class Wojownik : Postac
{
    public int Sila { get; set; }
    
    // MUSIMY nadpisać metodę abstrakcyjną!
    public override void Atakuj()
    {
        Console.WriteLine($"{Imie} atakuje mieczem z siłą {Sila}!");
    }
}
cs/Uzycie.cs C#
// ❌ TO NIE ZADZIAŁA:
// Postac postac = new Postac();  // BŁĄD KOMPILACJI!
// "Cannot create an instance of the abstract type 'Postac'"

// ✅ TO DZIAŁA:
Wojownik conan = new Wojownik();
conan.Imie = "Conan";
conan.PunktyZycia = 100;
conan.Sila = 15;

conan.PrzedstawSie();  // Działa – odziedziczona metoda
conan.Atakuj();        // Działa – nasza implementacja

// Możemy też użyć zmiennej typu Postac (polimorfizm):
Postac p = new Wojownik();  // ✅ OK – zmienna bazowa, obiekt pochodny
p.Atakuj();                 // Wywoła Wojownik.Atakuj()
Metoda abstrakcyjna nie ma ciała!

Zwróć uwagę na różnicę:

  • public virtual void Atakuj() { kod } – metoda virtual MA ciało
  • public abstract void Atakuj(); – metoda abstract NIE MA ciała (tylko średnik)

Metoda abstrakcyjna mówi „CO trzeba zrobić”, ale nie „JAK” – to zdefiniują klasy pochodne.

04

Metody abstrakcyjne – wymuszanie implementacji

Metoda abstract to „obietnica” – klasa bazowa mówi: „będzie taka metoda”, ale klasa pochodna musi ją zaimplementować.

cs/MetodyAbstrakcyjne.cs C#
public abstract class Ksztalt
{
    public string Nazwa { get; set; }
    public string Kolor { get; set; }
    
    // Każdy kształt MUSI umieć obliczyć swoje pole
    public abstract double ObliczPole();
    
    // Każdy kształt MUSI umieć obliczyć swój obwód
    public abstract double ObliczObwod();
    
    // Zwykła metoda – wspólna dla wszystkich
    public void WyswietlInfo()
    {
        Console.WriteLine($"{Nazwa} ({Kolor})");
        Console.WriteLine($"Pole: {ObliczPole():F2}");
        Console.WriteLine($"Obwód: {ObliczObwod():F2}");
    }
}

public class Prostokat : Ksztalt
{
    public double Szerokosc { get; set; }
    public double Wysokosc { get; set; }
    
    public override double ObliczPole()
    {
        return Szerokosc * Wysokosc;
    }
    
    public override double ObliczObwod()
    {
        return 2 * (Szerokosc + Wysokosc);
    }
}

public class Kolo : Ksztalt
{
    public double Promien { get; set; }
    
    public override double ObliczPole()
    {
        return Math.PI * Promien * Promien;
    }
    
    public override double ObliczObwod()
    {
        return 2 * Math.PI * Promien;
    }
}
cs/UzycieKsztalow.cs C#
Prostokat p = new Prostokat 
{ 
    Nazwa = "Prostokąt", 
    Kolor = "Niebieski", 
    Szerokosc = 5, 
    Wysokosc = 3 
};

Kolo k = new Kolo 
{ 
    Nazwa = "Koło", 
    Kolor = "Czerwony", 
    Promien = 4 
};

p.WyswietlInfo();
// Prostokąt (Niebieski)
// Pole: 15.00
// Obwód: 16.00

k.WyswietlInfo();
// Koło (Czerwony)
// Pole: 50.27
// Obwód: 25.13
Co się stanie, jeśli zapomnisz zaimplementować metodę abstrakcyjną?
public class Trojkat : Ksztalt
{
    public double Podstawa { get; set; }
    public double Wysokosc { get; set; }
    
    // Zapomniałem ObliczPole() i ObliczObwod()!
}

// BŁĄD KOMPILACJI:
// 'Trojkat' does not implement inherited abstract member 
// 'Ksztalt.ObliczPole()' and 'Ksztalt.ObliczObwod()'

Kompilator Cię ochroni! Nie da się skompilować kodu bez implementacji wszystkich metod abstrakcyjnych.

05

Ewolucja kodu – od virtual do abstract

Zobaczmy pełną transformację kodu z poprzedniego materiału o dziedziczeniu.

Etap 1: Bez dziedziczenia (duplikacja)

Trzy klasy z powtarzającym się kodem – to już znamy.

Etap 2: Z dziedziczeniem (virtual) – ale z problemami

cs/Etap2_Virtual.cs C#
public class Postac  // Zwykła klasa
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    public virtual void Atakuj()
    {
        Console.WriteLine("Atakuję jakoś...");  // 😕 Bezsensowna implementacja
    }
}

public class Wojownik : Postac
{
    public override void Atakuj()
    {
        Console.WriteLine($"{Imie} atakuje mieczem!");
    }
}

public class Mag : Postac
{
    // 😱 Programista ZAPOMNIAŁ nadpisać Atakuj()!
    // Kod się skompiluje, ale Mag będzie "atakował jakoś..."
}

// Problemy:
Postac p = new Postac();  // 😕 Można utworzyć – a nie powinno się dać!
Mag m = new Mag();
m.Atakuj();            // 😕 "Atakuję jakoś..." – błąd logiczny!

Etap 3: Z abstract – rozwiązanie wszystkich problemów! ✅

cs/Etap3_Abstract.cs C#
public abstract class Postac  // Klasa abstrakcyjna
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    // Metoda wspólna – ma implementację
    public void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Imie}");
    }
    
    public abstract void Atakuj();  // ✅ Brak implementacji – MUSI być nadpisana!
}

public class Wojownik : Postac
{
    public int Sila { get; set; }
    
    public override void Atakuj()  // ✅ MUSI być – inaczej błąd kompilacji
    {
        Console.WriteLine($"{Imie} atakuje mieczem!");
    }
}

public class Mag : Postac
{
    public int Mana { get; set; }
    
    public override void Atakuj()  // ✅ Musi być – kompilator wymusza!
    {
        Console.WriteLine($"{Imie} rzuca kulę ognia!");
    }
}

// Teraz:
// Postac p = new Postac();  // ❌ BŁĄD KOMPILACJI! Nie można utworzyć
Wojownik w = new Wojownik();  // ✅ OK
Mag m = new Mag();              // ✅ OK – Mag MA Atakuj() (musiał zaimplementować!)

Porównanie

virtual (zwykła klasa)

  • ✅ Można dziedziczyć
  • ❌ Można utworzyć instancję bazową
  • ❌ Trzeba wymyślić „domyślną” implementację
  • ❌ Można zapomnieć nadpisać
  • ❌ Błędy wykryte w runtime

abstract (klasa abstrakcyjna)

  • ✅ Można dziedziczyć
  • ✅ NIE można utworzyć instancji bazowej
  • ✅ Brak sztucznej implementacji
  • ✅ MUSISZ nadpisać – kompilator wymusza
  • ✅ Błędy wykryte przy kompilacji
06

virtual vs abstract – kiedy którego użyć?

Cechavirtualabstract
Ma implementację? ✅ Tak, domyślną ❌ Nie, tylko deklaracja
Czy trzeba nadpisać? ❌ Opcjonalnie ✅ Obowiązkowo
W jakiej klasie? Zwykłej lub abstrakcyjnej Tylko abstrakcyjnej
Słowo w override? override override
Kiedy używać? Jest sensowne zachowanie domyślne Każda klasa pochodna MUSI mieć własne
cs/VirtualVsAbstract.cs C#
public abstract class Zwierze
{
    public string Nazwa { get; set; }
    
    // ABSTRACT: Każde zwierzę wydaje INNY dźwięk – nie ma sensownego domyślnego
    public abstract void WydajDzwiek();
    
    // VIRTUAL: Większość zwierząt je podobnie, ale niektóre mogą inaczej
    public virtual void Jedz()
    {
        Console.WriteLine($"{Nazwa} je.");  // Sensowne domyślne zachowanie
    }
    
    // ZWYKŁA: Wszystkie zwierzęta śpią tak samo
    public void Spij()
    {
        Console.WriteLine($"{Nazwa} śpi. Zzz...");
    }
}

public class Pies : Zwierze
{
    // MUSI nadpisać WydajDzwiek() – abstract
    public override void WydajDzwiek()
    {
        Console.WriteLine("Hau hau!");
    }
    
    // MOŻE nadpisać Jedz() – virtual (ale nie musi)
    // Jeśli nie nadpisze, użyje domyślnej wersji
}

public class Waz : Zwierze
{
    public override void WydajDzwiek()
    {
        Console.WriteLine("Sss...");
    }
    
    // Wąż je inaczej – nadpisujemy virtual
    public override void Jedz()
    {
        Console.WriteLine($"{Nazwa} połyka ofiarę w całości!");
    }
}
Zasada decyzji

Zadaj sobie pytanie: „Czy istnieje sensowne zachowanie domyślne?”

  • TAK → użyj virtual z domyślną implementacją
  • NIE → użyj abstract bez implementacji
07

Właściwości abstrakcyjne

Nie tylko metody mogą być abstrakcyjne – właściwości też! To przydatne, gdy chcesz wymusić, aby klasy pochodne dostarczyły pewne dane.

cs/WlasciwosciAbstrakcyjne.cs C#
public abstract class Pojazd
{
    public string Marka { get; set; }
    
    // Właściwość abstrakcyjna – każdy pojazd MUSI określić liczbę kół
    public abstract int LiczbaKol { get; }
    
    // Właściwość abstrakcyjna z get i set
    public abstract double MaxPredkosc { get; set; }
    
    public void WyswietlInfo()
    {
        Console.WriteLine($"{Marka}: {LiczbaKol} koła, max {MaxPredkosc} km/h");
    }
}

public class Samochod : Pojazd
{
    // Implementacja właściwości abstrakcyjnej (tylko get)
    public override int LiczbaKol => 4;  // Zawsze 4
    
    // Implementacja właściwości abstrakcyjnej (get i set)
    private double _maxPredkosc;
    public override double MaxPredkosc
    {
        get => _maxPredkosc;
        set => _maxPredkosc = value > 0 ? value : 0;
    }
}

public class Motocykl : Pojazd
{
    public override int LiczbaKol => 2;  // Zawsze 2
    
    public override double MaxPredkosc { get; set; }
}

public class Trojkolowiec : Pojazd
{
    public override int LiczbaKol => 3;  // Zawsze 3
    
    public override double MaxPredkosc { get; set; }
}
cs/UzycieWlasciwosci.cs C#
Samochod auto = new Samochod { Marka = "Toyota", MaxPredkosc = 180 };
Motocykl motor = new Motocykl { Marka = "Yamaha", MaxPredkosc = 220 };

auto.WyswietlInfo();   // Toyota: 4 koła, max 180 km/h
motor.WyswietlInfo();  // Yamaha: 2 koła, max 220 km/h
08

Klasa częściowo abstrakcyjna – mix metod

Klasa abstrakcyjna może zawierać mieszankę metod abstrakcyjnych, wirtualnych i zwykłych. To daje elastyczność!

cs/MixMetod.cs C#
public abstract class Dokument
{
    public string Tytul { get; set; }
    public DateTime DataUtworzenia { get; set; } = DateTime.Now;
    
    // ABSTRACT – każdy dokument ma INNY format, MUSI być nadpisana
    public abstract string Formatuj();
    
    // VIRTUAL – domyślne zachowanie, ale MOŻNA nadpisać
    public virtual void Drukuj()
    {
        Console.WriteLine("=== DRUKOWANIE ===");
        Console.WriteLine(Formatuj());
        Console.WriteLine("===================");
    }
    
    // ZWYKŁA – wspólna dla wszystkich, NIE MOŻNA nadpisać
    public void WyswietlMetadane()
    {
        Console.WriteLine($"Tytuł: {Tytul}");
        Console.WriteLine($"Utworzono: {DataUtworzenia:d}");
    }
}

public class Raport : Dokument
{
    public string Autor { get; set; }
    public string Tresc { get; set; }
    
    // MUSI zaimplementować Formatuj() – abstract
    public override string Formatuj()
    {
        return $"RAPORT: {Tytul}\nAutor: {Autor}\n\n{Tresc}";
    }
    
    // MOŻE nadpisać Drukuj() – virtual
    public override void Drukuj()
    {
        Console.WriteLine("*** RAPORT OFICJALNY ***");
        base.Drukuj();  // Wywołaj bazową wersję
        Console.WriteLine("*** KONIEC RAPORTU ***");
    }
}

public class Notatka : Dokument
{
    public string Tresc { get; set; }
    
    // MUSI zaimplementować
    public override string Formatuj()
    {
        return $"[Notatka] {Tytul}: {Tresc}";
    }
    
    // NIE nadpisuje Drukuj() – użyje domyślnej wersji
}
abstract
Brak ciała
MUSI nadpisać
virtual
Ma ciało
MOŻE nadpisać
zwykła
Ma ciało
NIE MOŻE nadpisać
09

Hierarchia z klasami abstrakcyjnymi

Klasa abstrakcyjna może dziedziczyć po innej klasie abstrakcyjnej. Tworzy to hierarchię, gdzie metody abstrakcyjne „przepływają w dół” aż ktoś je zaimplementuje.

cs/HierarchiaAbstract.cs C#
// Poziom 1: Najbardziej ogólna klasa abstrakcyjna
public abstract class Istota
{
    public string Nazwa { get; set; }
    
    public abstract void Zyj();  // Każda istota żyje jakoś
}

// Poziom 2: Nadal abstrakcyjna – dodaje nowe wymagania
public abstract class Zwierze : Istota
{
    public int Wiek { get; set; }
    
    // Implementuje Zyj() z Istota
    public override void Zyj()
    {
        Console.WriteLine($"{Nazwa} żyje jako zwierzę");
    }
    
    // Dodaje NOWĄ metodę abstrakcyjną
    public abstract void WydajDzwiek();
}

// Poziom 3: Nadal abstrakcyjna – bardziej szczegółowa
public abstract class Ssak : Zwierze
{
    public bool MaSiersc { get; set; } = true;
    
    // NIE implementuje WydajDzwiek() – nadal abstract!
    // Dodaje nową metodę abstrakcyjną
    public abstract void KarmMlodem();
}

// Poziom 4: Klasa KONKRETNA – musi zaimplementować WSZYSTKO!
public class Pies : Ssak
{
    public string Rasa { get; set; }
    
    // MUSI zaimplementować WydajDzwiek() (z Zwierze)
    public override void WydajDzwiek()
    {
        Console.WriteLine("Hau hau!");
    }
    
    // MUSI zaimplementować KarmMlodem() (z Ssak)
    public override void KarmMlodem()
    {
        Console.WriteLine($"{Nazwa} karmi szczenięta mlekiem");
    }
}
abstract Istota
abstract Zyj()
abstract Zwierze
override Zyj() ✓
abstract WydajDzwiek()
abstract Ssak
WydajDzwiek() – nadal abstract!
abstract KarmMlodem()
class Pies
override WydajDzwiek() ✓
override KarmMlodem() ✓
🎉 Konkretna!
Klasa abstrakcyjna może „przekazać” metodę abstrakcyjną dalej

Jeśli klasa abstrakcyjna dziedziczy metodę abstrakcyjną i jej nie implementuje, ta metoda pozostaje abstrakcyjna. Pierwsza klasa konkretna w hierarchii musi zaimplementować wszystkie metody abstrakcyjne z całej hierarchii.

10

Kiedy używać klas abstrakcyjnych?

✅ Używaj abstract gdy:

SytuacjaPrzykład
Klasa bazowa to „koncept”, nie rzeczKształt (nie istnieje „po prostu kształt”)
Każda klasa pochodna MUSI zaimplementować coś inaczejObliczPole() – każdy kształt liczy inaczej
Nie ma sensownej domyślnej implementacjiWydajDzwiek() – „jakiś dźwięk” nie ma sensu
Chcesz wymusić implementacjęPołącz() – każda baza danych łączy się inaczej
Klasa ma wspólny kod + wymagane specyficzne metodyWspólne pola + abstract metody

❌ NIE używaj abstract gdy:

SytuacjaCo zamiast?
Potrzebujesz tworzyć obiekty tej klasyZwykła klasa
Jest sensowna domyślna implementacjavirtual
Nie ma wspólnego kodu, tylko wymagane metodyInterfejs (interface)
Klasa musi „dziedziczyć” po wielu źródłachInterfejsy

✅ Dobre przykłady abstract

  • Ksztalt → Kolo, Prostokat, Trojkat
  • Zwierze → Pies, Kot, Ptak
  • Pracownik → Programista, Manager
  • Dokument → PDF, Word, HTML
  • BazaDanych → MySQL, PostgreSQL, SQLite

❌ Złe przykłady abstract

  • Samochod jako abstract (można stworzyć „samochód”)
  • Uzytkownik jako abstract (konkretna osoba)
  • Tylko 1 klasa pochodna (niepotrzebne)
  • Brak wspólnego kodu (użyj interfejsu)
11

Klasa abstrakcyjna vs interfejs – wprowadzenie

Klasy abstrakcyjne i interfejsy to dwa sposoby definiowania „kontraktów”. Poznaj różnice, żeby wiedzieć, którego użyć.

Cechaabstract classinterface
Może mieć implementację? ✅ Tak (metody zwykłe i virtual) ❌ Nie (tylko deklaracje)*
Może mieć pola? ✅ Tak ❌ Nie
Może mieć konstruktor? ✅ Tak ❌ Nie
Wielokrotne dziedziczenie? ❌ Tylko jedna klasa ✅ Wiele interfejsów
Modyfikatory dostępu? ✅ Dowolne ❌ Wszystko publiczne
Kiedy używać? Wspólny kod + wymagane metody Tylko kontrakt „co umie”

* Od C# 8.0 interfejsy mogą mieć domyślne implementacje, ale to zaawansowany temat.

cs/AbstractVsInterface.cs C#
// KLASA ABSTRAKCYJNA – ma wspólny kod
public abstract class Zwierze
{
    public string Nazwa { get; set; }  // Pole!
    
    public void Oddychaj()              // Wspólna implementacja!
    {
        Console.WriteLine("Oddycham...");
    }
    
    public abstract void WydajDzwiek();
}

// INTERFEJS – tylko kontrakt
public interface IPlywa
{
    void Plyn();  // Tylko deklaracja – brak implementacji
}

// Klasa może dziedziczyć po 1 klasie i implementować WIELE interfejsów
public class Delfin : Zwierze, IPlywa
{
    public override void WydajDzwiek()
    {
        Console.WriteLine("Iii iii!");
    }
    
    public void Plyn()
    {
        Console.WriteLine("Płynę!");
    }
}
Zasada wyboru
  • Masz wspólny kod do współdzielenia? → abstract class
  • Definiujesz tylko „co coś umie”? → interface
  • Potrzebujesz „dziedziczyć” po wielu? → interface

Możesz też łączyć: jedna klasa abstrakcyjna + wiele interfejsów!

12

Częste błędy

❌ Błąd 1: Próba utworzenia instancji klasy abstrakcyjnej

❌ Źle

public abstract class Zwierze { }

Zwierze z = new Zwierze();
// BŁĄD: Cannot create an instance
// of the abstract type 'Zwierze'

✅ Dobrze

public class Pies : Zwierze { }

Zwierze z = new Pies();  // OK!
Pies p = new Pies();     // OK!

❌ Błąd 2: Metoda abstract z ciałem

❌ Źle

public abstract void Atakuj()
{
    Console.WriteLine("Atak!");
}
// BŁĄD: Abstract method cannot
// have a body

✅ Dobrze

// Abstract – bez ciała:
public abstract void Atakuj();

// Lub virtual – z ciałem:
public virtual void Atakuj()
{
    Console.WriteLine("Atak!");
}

❌ Błąd 3: Metoda abstract w zwykłej klasie

❌ Źle

public class Postac  // Brak abstract!
{
    public abstract void Atakuj();
}
// BŁĄD: 'Postac' cannot declare
// abstract members

✅ Dobrze

public abstract class Postac
{
    public abstract void Atakuj();
}
// OK!

❌ Błąd 4: Niezaimplementowanie wszystkich metod abstrakcyjnych

❌ Źle

public abstract class Ksztalt
{
    public abstract double Pole();
    public abstract double Obwod();
}

public class Kolo : Ksztalt
{
    public override double Pole()
        => 3.14 * r * r;
    // Brak Obwod()!
}
// BŁĄD: Does not implement 'Obwod'

✅ Dobrze

public class Kolo : Ksztalt
{
    public override double Pole()
        => 3.14 * r * r;
    
    public override double Obwod()
        => 2 * 3.14 * r;
}
// OK! Wszystko zaimplementowane

❌ Błąd 5: Brak override w klasie pochodnej

❌ Źle

public class Pies : Zwierze
{
    public void WydajDzwiek()  // Brak override!
    {
        Console.WriteLine("Hau!");
    }
}
// Ostrzeżenie: hides inherited
// member 'Zwierze.WydajDzwiek'

✅ Dobrze

public class Pies : Zwierze
{
    public override void WydajDzwiek()
    {
        Console.WriteLine("Hau!");
    }
}
13

Podsumowanie

Kluczowe pojęcia
  • abstract class = klasa, której nie można utworzyć instancji
  • abstract method = metoda bez ciała, MUSI być nadpisana
  • virtual method = metoda z ciałem, MOŻE być nadpisana
  • override = nadpisuję metodę (abstract lub virtual)
  • Wymuszenie implementacji = kompilator pilnuje, że wszystko jest zaimplementowane

Porównanie typów metod

Typ metodyMa ciało?Trzeba nadpisać?Gdzie może być?
abstract❌ Nie✅ ObowiązkowoTylko w abstract class
virtual✅ Tak❌ OpcjonalnieW dowolnej klasie
zwykła✅ Tak❌ Nie możnaW dowolnej klasie

Kiedy używać?

Pytanie
Czy można sensownie utworzyć obiekt tej klasy?
TAK
Zwykła klasa
(ewentualnie z virtual)
NIE
abstract class
cs/Schemat.cs C#
public abstract class KlasaBazowa
{
    // Zwykłe pola i właściwości
    public string Pole { get; set; }
    
    // Metoda abstrakcyjna – MUSI być nadpisana
    public abstract void MetodaWymagana();
    
    // Metoda virtual – MOŻE być nadpisana
    public virtual void MetodaOpcjonalna() { }
    
    // Zwykła metoda – NIE można nadpisać
    public void MetodaWspolna() { }
}

public class KlasaKonkretna : KlasaBazowa
{
    public override void MetodaWymagana()
    {
        // Implementacja WYMAGANA
    }
    
    // MetodaOpcjonalna – możemy nadpisać lub nie
    // MetodaWspolna – używamy jak jest
}
Zadania

Zadania praktyczne

📝 Zadanie 1: Kształty geometryczne

Utwórz hierarchię klas:

  • abstract Ksztalt: Nazwa, Kolor, abstract ObliczPole(), abstract ObliczObwod(), WyswietlInfo()
  • Prostokat: Szerokosc, Wysokosc
  • Kolo: Promien
  • Trojkat: PodstawaA, PodstawaB, PodstawaC, Wysokosc

Utwórz tablicę Ksztalt[] z różnymi kształtami i wyświetl info dla każdego.

💡 Podpowiedź: Obwód trójkąta = A + B + C, pole = 0.5 * podstawa * wysokość

📝 Zadanie 2: System płatności

Utwórz:

  • abstract MetodaPlatnosci: NazwaMetody, abstract Zaplac(decimal kwota), virtual SprawdzSaldo()
  • KartaKredytowa: NumerKarty, Limit, override Zaplac() (sprawdza limit)
  • PayPal: Email, Saldo, override Zaplac() (sprawdza saldo)
  • Blik: NumerTelefonu, override Zaplac() (generuje kod)

💡 Podpowiedź: Metoda Zaplac() powinna zwracać bool (sukces/porażka)

📝 Zadanie 3: Hierarchia pracowników

Przekształć zadanie z dziedziczenia na wersję z abstract:

  • abstract Pracownik: Imie, Nazwisko, PensjaBase, abstract ObliczWynagrodzenie()
  • Programista: LiczbaZakonczychProjektow (+500 za każdy)
  • Sprzedawca: WartoscSprzedazy (10% prowizji)
  • Manager: LiczbaPodwladnych (+300 za każdego)

💡 Podpowiedź: Teraz nie można utworzyć „po prostu Pracownika”

⭐ Zadanie 4: System powiadomień

Utwórz:

  • abstract Powiadomienie: Tytul, Tresc, DataWyslania, abstract Wyslij(), virtual Formatuj()
  • Email: AdresOdbiorcy, AdresNadawcy
  • SMS: NumerTelefonu (max 160 znaków!)
  • PushNotification: DeviceToken, Priorytet

Każda klasa ma własną implementację Wyslij() i może nadpisać Formatuj().

💡 Podpowiedź: SMS może skracać treść jeśli za długa

⭐⭐ Zadanie 5: Silnik gry – jednostki bojowe

Utwórz rozbudowaną hierarchię dla gry:

  • abstract Jednostka: Nazwa, HP, Pozycja(X,Y), abstract Ruszaj(), abstract Atakuj(Jednostka cel), virtual OtrzymajObrazenia(int dmg)
  • abstract JednostkaNaziemna : Jednostka – PredkoscChodzenia
  • abstract JednostkaPowietrzna : Jednostka – PredkoscLotu, WysokoscLotu
  • Piechota : JednostkaNaziemna
  • Czolg : JednostkaNaziemna – Pancerz (modyfikuje OtrzymajObrazenia)
  • Samolot : JednostkaPowietrzna
  • Helikopter : JednostkaPowietrzna – MozeZatrzymacSieWPowietrzu

Utwórz symulację bitwy między różnymi jednostkami.

💡 Podpowiedź: Czołg może mieć override OtrzymajObrazenia() redukujące obrażenia o wartość pancerza