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!
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…
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!
- 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!
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!
Atakuj() ← „jakoś…”
Można zapomnieć override
← nie można utworzyć!
abstract void Atakuj()
← MUSISZ nadpisać!
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ć.
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!
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
| Cecha | Co 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(); |
────────────────
Imie, PunktyZycia
abstract void Atakuj();
override Atakuj() ✓
override Atakuj() ✓
override Atakuj() ✓
Podstawowa składnia
Aby oznaczyć klasę jako abstrakcyjną, dodajemy słowo kluczowe abstract przed class.
// 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}!"); } }
// ❌ 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()
Zwróć uwagę na różnicę:
public virtual void Atakuj() { kod }– metoda virtual MA ciałopublic 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.
Metody abstrakcyjne – wymuszanie implementacji
Metoda abstract to „obietnica” – klasa bazowa mówi: „będzie taka metoda”, ale klasa pochodna musi ją zaimplementować.
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; } }
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
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.
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
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! ✅
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
virtual vs abstract – kiedy którego użyć?
| Cecha | virtual | abstract |
|---|---|---|
| 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 |
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!"); } }
Zadaj sobie pytanie: „Czy istnieje sensowne zachowanie domyślne?”
- TAK → użyj
virtualz domyślną implementacją - NIE → użyj
abstractbez implementacji
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.
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; } }
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
Klasa częściowo abstrakcyjna – mix metod
Klasa abstrakcyjna może zawierać mieszankę metod abstrakcyjnych, wirtualnych i zwykłych. To daje elastyczność!
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 }
MUSI nadpisać
MOŻE nadpisać
NIE MOŻE nadpisać
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.
// 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 WydajDzwiek()
abstract KarmMlodem()
override KarmMlodem() ✓
🎉 Konkretna!
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.
Kiedy używać klas abstrakcyjnych?
✅ Używaj abstract gdy:
| Sytuacja | Przykład |
|---|---|
| Klasa bazowa to „koncept”, nie rzecz | Kształt (nie istnieje „po prostu kształt”) |
| Każda klasa pochodna MUSI zaimplementować coś inaczej | ObliczPole() – każdy kształt liczy inaczej |
| Nie ma sensownej domyślnej implementacji | WydajDzwiek() – „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 metody | Wspólne pola + abstract metody |
❌ NIE używaj abstract gdy:
| Sytuacja | Co zamiast? |
|---|---|
| Potrzebujesz tworzyć obiekty tej klasy | Zwykła klasa |
| Jest sensowna domyślna implementacja | virtual |
| Nie ma wspólnego kodu, tylko wymagane metody | Interfejs (interface) |
| Klasa musi „dziedziczyć” po wielu źródłach | Interfejsy |
✅ 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)
Klasa abstrakcyjna vs interfejs – wprowadzenie
Klasy abstrakcyjne i interfejsy to dwa sposoby definiowania „kontraktów”. Poznaj różnice, żeby wiedzieć, którego użyć.
| Cecha | abstract class | interface |
|---|---|---|
| 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.
// 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ę!"); } }
- 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!
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!");
}
}
Podsumowanie
- 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 metody | Ma ciało? | Trzeba nadpisać? | Gdzie może być? |
|---|---|---|---|
| abstract | ❌ Nie | ✅ Obowiązkowo | Tylko w abstract class |
| virtual | ✅ Tak | ❌ Opcjonalnie | W dowolnej klasie |
| zwykła | ✅ Tak | ❌ Nie można | W dowolnej klasie |
Kiedy używać?
(ewentualnie z virtual)
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 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