Dziedziczenie w C# – nie pisz tego samego dwa razy

Nauczysz się tworzyć hierarchie klas, eliminować duplikację kodu i budować elastyczne, łatwe w utrzymaniu aplikacje. To jeden z filarów programowania obiektowego!

dziedziczenie klasa bazowa virtual/override base DRY
01

Problem – powtarzający się kod

Wyobraź sobie, że tworzysz grę z różnymi postaciami: Wojownik, Mag i Łucznik. Każda postać ma imię, punkty życia i może atakować. Zobaczmy, jak wyglądałby kod bez dziedziczenia:

cs/BezDziedziczenia.cs C#
// ❌ PROBLEM: Powtarzający się kod w każdej klasie!

public class Wojownik
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    public int Poziom { get; set; }
    
    public void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Imie}, poziom {Poziom}");
    }
    
    public void Atakuj()
    {
        Console.WriteLine($"{Imie} atakuje mieczem!");
    }
}

public class Mag
{
    public string Imie { get; set; }           // To samo co w Wojownik!
    public int PunktyZycia { get; set; }      // To samo!
    public int Poziom { get; set; }           // To samo!
    public int Mana { get; set; }             // Tylko to jest unikalne
    
    public void PrzedstawSie()                 // To samo!
    {
        Console.WriteLine($"Jestem {Imie}, poziom {Poziom}");
    }
    
    public void Atakuj()
    {
        Console.WriteLine($"{Imie} rzuca kulę ognia!");
    }
}

public class Lucznik
{
    public string Imie { get; set; }           // Znowu to samo!
    public int PunktyZycia { get; set; }      // Znowu!
    public int Poziom { get; set; }           // Znowu!
    public int Strzaly { get; set; }
    
    public void PrzedstawSie()                 // Znowu!
    {
        Console.WriteLine($"Jestem {Imie}, poziom {Poziom}");
    }
    
    public void Atakuj()
    {
        Console.WriteLine($"{Imie} strzela z łuku!");
    }
}
Co jest nie tak z tym kodem?
  • Duplikacja – Imie, PunktyZycia, Poziom i PrzedstawSie() powtarzają się 3 razy!
  • Trudne utrzymanie – Chcesz dodać pole „Doswiadczenie”? Musisz zmienić 3 klasy!
  • Ryzyko błędów – Łatwo zapomnieć zmienić jedną z klas
  • Rozrastanie się – Co jeśli będzie 20 typów postaci?

Ten problem rozwiązuje dziedziczenie – zamiast pisać to samo wiele razy, definiujemy wspólne cechy raz i pozwalamy innym klasom je „odziedziczyć”.

02

Co to jest dziedziczenie?

Dziedziczenie to mechanizm OOP, który pozwala klasie „przejąć” pola i metody innej klasy. Klasa dziedzicząca automatycznie otrzymuje wszystkie publiczne i chronione składowe klasy bazowej.

Analogia: Rodzina

Pomyśl o dziedziczeniu jak o cechach rodzinnych:

  • Klasa bazowa (rodzic) = Ssak – ma cechy wspólne: oddycha, je, porusza się
  • Klasa pochodna (dziecko) = Pies – dziedziczy cechy ssaka + dodaje własne: szczeka, aportuje
  • Klasa pochodna (dziecko) = Kot – dziedziczy cechy ssaka + dodaje własne: miauczy, mruczy

Pies i Kot nie muszą definiować „oddychania” – dostają to automatycznie od Ssaka!

Postac (klasa bazowa)
Imie
PunktyZycia
Poziom
PrzedstawSie()
↓ dziedziczy
↓ dziedziczy
↓ dziedziczy
Wojownik
+ Sila
+ AtakMieczem()
Mag
+ Mana
+ RzucCzar()
Lucznik
+ Strzaly
+ Strzelaj()

↑ Wszystkie klasy automatycznie mają Imie, PunktyZycia, Poziom i PrzedstawSie()

Terminologia

TerminInne nazwyOpis
Klasa bazowaNadklasa, klasa rodzica, base classKlasa, po której dziedziczymy (Postac)
Klasa pochodnaPodklasa, klasa dziecka, derived classKlasa dziedzicząca (Wojownik, Mag)
DziedziczenieInheritanceRelacja „jest rodzajem” (Wojownik jest Postacią)
Test „jest rodzajem” (is-a)

Dziedziczenie ma sens, gdy możesz powiedzieć „X jest rodzajem Y„:

  • ✅ Wojownik jest rodzajem Postaci – TAK, dziedziczenie
  • ✅ Pies jest rodzajem Zwierzęcia – TAK, dziedziczenie
  • ❌ Samochód jest rodzajem Silnika – NIE! Samochód ma silnik (kompozycja)
03

Podstawowa składnia dziedziczenia

W C# dziedziczenie zapisujemy za pomocą dwukropka po nazwie klasy.

cs/Skladnia.cs C#
// Klasa BAZOWA (rodzic)
public class Postac
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    public void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Imie}");
    }
}

// Klasa POCHODNA (dziecko) – dziedziczy po Postac
public class Wojownik : Postac
{
    // Wojownik automatycznie MA: Imie, PunktyZycia, PrzedstawSie()
    
    // Dodajemy tylko to, co jest UNIKALNE dla Wojownika
    public int Sila { get; set; }
    
    public void AtakMieczem()
    {
        Console.WriteLine($"{Imie} atakuje mieczem z siłą {Sila}!");
    }
}
cs/Uzycie.cs C#
// Tworzenie obiektu klasy pochodnej
Wojownik conan = new Wojownik();

// Mamy dostęp do składowych odziedziczonych z Postac:
conan.Imie = "Conan";           // ← z klasy Postac
conan.PunktyZycia = 100;        // ← z klasy Postac
conan.PrzedstawSie();            // ← z klasy Postac → "Jestem Conan"

// I do składowych własnych Wojownika:
conan.Sila = 15;                 // ← tylko w Wojownik
conan.AtakMieczem();             // ← tylko w Wojownik → "Conan atakuje mieczem..."
W C# można dziedziczyć tylko po JEDNEJ klasie!

C# nie obsługuje dziedziczenia wielokrotnego (jedna klasa nie może mieć dwóch rodziców). To ograniczenie zapobiega wielu problemom.

// ❌ TO NIE ZADZIAŁA:
public class Centaur : Czlowiek, Kon  // Błąd!

Jeśli potrzebujesz „cech” z wielu źródeł, użyj interfejsów (to temat na inny materiał).

04

Ewolucja kodu – przed i po dziedziczeniu

Zobaczmy pełną transformację kodu z sekcji 1. To najlepiej pokazuje korzyści dziedziczenia.

❌ PRZED – bez dziedziczenia (60+ linii powtórzonego kodu)

cs/Przed.cs C#
// 😰 3 klasy, każda powtarza te same pola i metody

public class Wojownik
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    public int Poziom { get; set; }
    public int Sila { get; set; }
    
    public void PrzedstawSie() { Console.WriteLine($"Jestem {Imie}, poziom {Poziom}"); }
    public void Atakuj() { Console.WriteLine($"{Imie} atakuje mieczem!"); }
}

public class Mag
{
    public string Imie { get; set; }           // DUPLIKAT
    public int PunktyZycia { get; set; }      // DUPLIKAT
    public int Poziom { get; set; }           // DUPLIKAT
    public int Mana { get; set; }
    
    public void PrzedstawSie() { Console.WriteLine($"Jestem {Imie}, poziom {Poziom}"); }  // DUPLIKAT
    public void Atakuj() { Console.WriteLine($"{Imie} rzuca zaklęcie!"); }
}

public class Lucznik
{
    public string Imie { get; set; }           // DUPLIKAT
    public int PunktyZycia { get; set; }      // DUPLIKAT
    public int Poziom { get; set; }           // DUPLIKAT
    public int Strzaly { get; set; }
    
    public void PrzedstawSie() { Console.WriteLine($"Jestem {Imie}, poziom {Poziom}"); }  // DUPLIKAT
    public void Atakuj() { Console.WriteLine($"{Imie} strzela z łuku!"); }
}

✅ PO – z dziedziczeniem (DRY – Don’t Repeat Yourself)

cs/Po_KlasaBazowa.cs C#
// 😊 Wspólne cechy definiujemy RAZ w klasie bazowej

public class Postac
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    public int Poziom { get; set; }
    
    public void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Imie}, poziom {Poziom}");
    }
    
    // Metoda virtual – klasy pochodne mogą ją nadpisać
    public virtual void Atakuj()
    {
        Console.WriteLine($"{Imie} atakuje!");
    }
}
cs/Po_KlasyPochodne.cs C#
// Każda klasa pochodna definiuje TYLKO to, co jest unikalne

public class Wojownik : Postac
{
    public int Sila { get; set; }
    
    public override void Atakuj()
    {
        Console.WriteLine($"{Imie} atakuje mieczem z siłą {Sila}!");
    }
}

public class Mag : Postac
{
    public int Mana { get; set; }
    
    public override void Atakuj()
    {
        Console.WriteLine($"{Imie} rzuca kulę ognia! (Mana: {Mana})");
    }
    
    public void Lecz(Postac cel)
    {
        cel.PunktyZycia += 20;
        Console.WriteLine($"{Imie} leczy {cel.Imie}!");
    }
}

public class Lucznik : Postac
{
    public int Strzaly { get; set; }
    
    public override void Atakuj()
    {
        if (Strzaly > 0)
        {
            Strzaly--;
            Console.WriteLine($"{Imie} strzela! (Zostało strzał: {Strzaly})");
        }
        else
        {
            Console.WriteLine($"{Imie} nie ma strzał!");
        }
    }
}

Porównanie korzyści

❌ Bez dziedziczenia

  • ~60 linii kodu
  • 9 powtórzonych pól (3 × 3)
  • 3 powtórzone metody PrzedstawSie()
  • Zmiana wymaga edycji 3 klas
  • Łatwo o niespójności

✅ Z dziedziczeniem

  • ~40 linii kodu (-33%)
  • Pola wspólne w jednym miejscu
  • PrzedstawSie() napisane RAZ
  • Zmiana w jednym miejscu
  • Gwarancja spójności
Chcesz dodać nowe pole dla wszystkich postaci?

Wystarczy dodać je tylko w klasie Postac – wszystkie klasy pochodne automatycznie je odziedziczą!

// Dodajemy w Postac:
public int Doswiadczenie { get; set; }

// Teraz KAŻDA postać (Wojownik, Mag, Łucznik) ma Doswiadczenie!
05

Konstruktory i słowo kluczowe base

Gdy klasa pochodna ma konstruktor, musi najpierw wywołać konstruktor klasy bazowej. Robimy to za pomocą słowa base.

Dlaczego base?

Pomyśl o budowie domu:

  • Klasa bazowa = fundamenty i ściany
  • Klasa pochodna = wykończenie i meble

Nie możesz ustawić mebli, jeśli nie masz najpierw ścian! Dlatego zawsze najpierw budujemy klasę bazową.

cs/KonstruktoryBase.cs C#
public class Postac
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    // Konstruktor klasy bazowej
    public Postac(string imie, int punktyZycia)
    {
        Imie = imie;
        PunktyZycia = punktyZycia;
        Console.WriteLine($"Tworzę postać: {imie}");
    }
}

public class Wojownik : Postac
{
    public int Sila { get; set; }
    
    // Konstruktor klasy pochodnej wywołuje konstruktor bazowy
    public Wojownik(string imie, int punktyZycia, int sila) : base(imie, punktyZycia)
    {
        Sila = sila;
        Console.WriteLine($"Wojownik gotowy z siłą {sila}!");
    }
}

public class Mag : Postac
{
    public int Mana { get; set; }
    
    public Mag(string imie, int mana) : base(imie, 80)  // Magowie zawsze mają 80 HP
    {
        Mana = mana;
    }
}
cs/UzycieKonstruktorow.cs C#
// Tworzenie obiektów
Wojownik conan = new Wojownik("Conan", 100, 15);
// Wyświetli:
// Tworzę postać: Conan       ← konstruktor Postac
// Wojownik gotowy z siłą 15! ← konstruktor Wojownik

Mag gandalf = new Mag("Gandalf", 200);
// Wyświetli:
// Tworzę postać: Gandalf     ← konstruktor Postac (HP=80 automatycznie)
1. new Wojownik(…)
Wywołanie
2. base(…)
Konstruktor Postac
3. Wojownik(…)
Reszta konstruktora

↑ Najpierw wykonuje się konstruktor bazowy, potem pochodny

Kiedy base jest wymagane?

Jeśli klasa bazowa nie ma konstruktora bezparametrowego, musisz explicite wywołać base(...) z odpowiednimi argumentami. W przeciwnym razie dostaniesz błąd kompilacji.

06

Modyfikator protected

protected to modyfikator dostępu stworzony specjalnie dla dziedziczenia. Pole/metoda protected jest dostępna w tej klasie i wszystkich klasach pochodnych, ale nie z zewnątrz.

public
Wszyscy
protected
Ta klasa + dzieci
private
Tylko ta klasa
cs/Protected.cs C#
public class Postac
{
    public string Imie { get; set; }
    
    protected int bazowePunktyAtaku = 10;  // Dostępne dla klas pochodnych
    
    private string tajneHaslo = "abc";   // Tylko dla tej klasy
}

public class Wojownik : Postac
{
    public int ObliczObrazenia()
    {
        return bazowePunktyAtaku * 2;  // ✅ OK – protected jest dostępne
        
        // return tajneHaslo;  // ❌ BŁĄD – private niedostępne!
    }
}

// Z zewnątrz:
Wojownik w = new Wojownik();
// w.bazowePunktyAtaku = 20;  // ❌ BŁĄD – protected niedostępne z zewnątrz!
Console.WriteLine(w.ObliczObrazenia());  // ✅ OK – publiczna metoda
ModyfikatorTa klasaKlasa pochodnaInne klasy
public
protected
private
Kiedy używać protected?

Używaj protected gdy:

  • Klasy pochodne potrzebują dostępu do tego pola/metody
  • Ale nie chcesz, żeby było dostępne z zewnątrz

Przykłady: wewnętrzne liczniki, pomocnicze metody, wartości bazowe do obliczeń.

07

Nadpisywanie metod – virtual i override

Klasa pochodna może zmienić zachowanie metody odziedziczonej z klasy bazowej. To nazywamy nadpisywaniem (override).

Jak to działa?

  1. W klasie bazowej oznaczamy metodę słowem virtual – „można mnie nadpisać”
  2. W klasie pochodnej używamy override – „nadpisuję tę metodę”
cs/VirtualOverride.cs C#
public class Zwierze
{
    public string Nazwa { get; set; }
    
    // virtual = "klasy pochodne mogą to zmienić"
    public virtual void WydajDzwiek()
    {
        Console.WriteLine("Jakiś dźwięk...");
    }
    
    // Metoda BEZ virtual – NIE można jej nadpisać
    public void Jedz()
    {
        Console.WriteLine($"{Nazwa} je.");
    }
}

public class Pies : Zwierze
{
    // override = "zmieniam zachowanie metody bazowej"
    public override void WydajDzwiek()
    {
        Console.WriteLine("Hau hau!");
    }
}

public class Kot : Zwierze
{
    public override void WydajDzwiek()
    {
        Console.WriteLine("Miau!");
    }
}

public class Krowa : Zwierze
{
    public override void WydajDzwiek()
    {
        Console.WriteLine("Muuu!");
    }
}
cs/UzycieOverride.cs C#
Pies reksio = new Pies { Nazwa = "Reksio" };
Kot filemon = new Kot { Nazwa = "Filemon" };
Krowa mucka = new Krowa { Nazwa = "Mućka" };

reksio.WydajDzwiek();   // Hau hau!
filemon.WydajDzwiek();  // Miau!
mucka.WydajDzwiek();    // Muuu!

// Metoda Jedz() jest odziedziczona bez zmian:
reksio.Jedz();           // Reksio je.
virtual i override muszą iść w parze!
  • Metoda bez virtual → nie można nadpisać (błąd kompilacji)
  • override bez virtual w klasie bazowej → błąd kompilacji
  • Możesz też użyć override i dodać virtual, żeby kolejne klasy mogły nadpisywać dalej
08

Rozszerzanie metod bazowych (base.Metoda)

Czasem nie chcemy całkowicie zastąpić metodę bazową, ale tylko ją rozszerzyć. Używamy wtedy base.NazwaMetody() wewnątrz override.

cs/BaseMetoda.cs C#
public class Postac
{
    public string Imie { get; set; }
    public int PunktyZycia { get; set; }
    
    public virtual void WyswietlStatystyki()
    {
        Console.WriteLine($"=== {Imie} ===");
        Console.WriteLine($"HP: {PunktyZycia}");
    }
}

public class Mag : Postac
{
    public int Mana { get; set; }
    public int Inteligencja { get; set; }
    
    public override void WyswietlStatystyki()
    {
        // Najpierw wywołaj wersję bazową
        base.WyswietlStatystyki();
        
        // Potem dodaj własne rzeczy
        Console.WriteLine($"Mana: {Mana}");
        Console.WriteLine($"INT: {Inteligencja}");
    }
}

public class Wojownik : Postac
{
    public int Sila { get; set; }
    public int Pancerz { get; set; }
    
    public override void WyswietlStatystyki()
    {
        base.WyswietlStatystyki();
        Console.WriteLine($"Siła: {Sila}");
        Console.WriteLine($"Pancerz: {Pancerz}");
    }
}
cs/UzycieBase.cs C#
Mag gandalf = new Mag 
{ 
    Imie = "Gandalf", 
    PunktyZycia = 80, 
    Mana = 200, 
    Inteligencja = 18 
};

gandalf.WyswietlStatystyki();
// Wyświetli:
// === Gandalf ===    ← z base.WyswietlStatystyki()
// HP: 80              ← z base.WyswietlStatystyki()
// Mana: 200           ← dodane przez Mag
// INT: 18             ← dodane przez Mag
base.WyswietlStatystyki()
=== Imie ===
HP: …
+
Własny kod Mag
Mana: …
INT: …
=
Pełny wynik
Wszystko razem!
Kiedy używać base.Metoda()?

Gdy chcesz rozszerzyć (dodać coś do) zachowania bazowego, a nie je całkowicie zastąpić.

  • ✅ Wyświetlanie statystyk – bazowe + własne
  • ✅ Zapisywanie do pliku – bazowe + własne pola
  • ❌ Wydawanie dźwięku – pies nie mówi „Jakiś dźwięk… Hau hau!”
09

Hierarchia klas – dziedziczenie wielopoziomowe

Klasa pochodna może być klasą bazową dla kolejnej klasy. Tworzy to hierarchię dziedziczenia.

cs/Hierarchia.cs C#
// Poziom 1 – najbardziej ogólna klasa
public class Istota
{
    public string Nazwa { get; set; }
    public bool CzyZyje { get; set; } = true;
}

// Poziom 2 – Postac dziedziczy po Istota
public class Postac : Istota
{
    public int PunktyZycia { get; set; }
    public int Poziom { get; set; }
    
    public virtual void Atakuj() { }
}

// Poziom 3 – Wojownik dziedziczy po Postac (i pośrednio po Istota)
public class Wojownik : Postac
{
    public int Sila { get; set; }
    
    public override void Atakuj()
    {
        Console.WriteLine($"{Nazwa} atakuje mieczem!");
    }
}

// Poziom 4 – Paladyn dziedziczy po Wojownik
public class Paladyn : Wojownik
{
    public int SwietaMoc { get; set; }
    
    public void UzyjSwietejMocy()
    {
        Console.WriteLine($"{Nazwa} używa świętej mocy!");
    }
}
Istota
Nazwa, CzyZyje
Postac
+ PunktyZycia, Poziom
Wojownik
+ Sila
Paladyn
+ SwietaMoc
cs/UzycieHierarchii.cs C#
Paladyn arthas = new Paladyn();

// Paladyn ma dostęp do WSZYSTKICH odziedziczonych składowych:
arthas.Nazwa = "Arthas";          // z Istota
arthas.CzyZyje = true;             // z Istota
arthas.PunktyZycia = 150;          // z Postac
arthas.Poziom = 20;                // z Postac
arthas.Sila = 18;                  // z Wojownik
arthas.SwietaMoc = 50;             // własne Paladyn

arthas.Atakuj();                    // z Wojownik
arthas.UzyjSwietejMocy();           // własne Paladyn
Nie przesadzaj z głębokością!

Zbyt głęboka hierarchia (5+ poziomów) staje się trudna do zrozumienia i utrzymania. Zazwyczaj 2-3 poziomy wystarczają.

10

Polimorfizm – wiele form, jeden interfejs

Polimorfizm oznacza, że możesz traktować obiekty różnych klas jednakowo, jeśli dziedziczą po tej samej klasie bazowej. To potężna technika!

Analogia: Pilot od telewizora

Jeden pilot może obsługiwać różne telewizory różnych marek. Dla użytkownika interfejs jest ten sam (przyciski), ale każdy telewizor reaguje po swojemu.

Podobnie: zmienna typu Postac może przechowywać Wojownika, Maga lub Łucznika – i każdy reaguje na Atakuj() po swojemu!

cs/Polimorfizm.cs C#
// Tablica POSTACI – ale przechowuje różne typy!
Postac[] druzyna = new Postac[3];

druzyna[0] = new Wojownik { Imie = "Conan", Sila = 15 };
druzyna[1] = new Mag { Imie = "Gandalf", Mana = 200 };
druzyna[2] = new Lucznik { Imie = "Legolas", Strzaly = 30 };

// Każdy atakuje PO SWOJEMU, mimo że wywołujemy tę samą metodę!
foreach (Postac postac in druzyna)
{
    postac.Atakuj();
}

// Wyświetli:
// Conan atakuje mieczem z siłą 15!       ← Wojownik.Atakuj()
// Gandalf rzuca kulę ognia! (Mana: 200)  ← Mag.Atakuj()
// Legolas strzela! (Zostało strzał: 29)  ← Lucznik.Atakuj()

Praktyczne zastosowanie – metoda przyjmująca klasę bazową

cs/MetodaPolimorficzna.cs C#
// Ta metoda działa z KAŻDĄ postacią!
public void WykonajTure(Postac postac)
{
    Console.WriteLine($"--- Tura: {postac.Imie} ---");
    postac.PrzedstawSie();
    postac.Atakuj();  // Wywoła odpowiednią wersję!
}

// Użycie:
Wojownik w = new Wojownik { Imie = "Conan" };
Mag m = new Mag { Imie = "Gandalf" };

WykonajTure(w);  // Działa z Wojownikiem
WykonajTure(m);  // Działa z Magiem
// Jedna metoda obsługuje wszystkie typy postaci!
Korzyść polimorfizmu

Nie musisz pisać osobnej metody WykonajTureWojownika(), WykonajTureMaga(), itd. Jedna metoda działa z wszystkimi klasami pochodnymi!

11

Kiedy używać dziedziczenia?

✅ Używaj dziedziczenia gdy:

SytuacjaPrzykład
Relacja „jest rodzajem” (is-a)Pies jest Zwierzęciem
Wspólne cechy wielu klasWszystkie pojazdy mają prędkość
Różne zachowania tej samej akcjiKażde zwierzę inaczej je
Hierarchia naturalnaSsak → Pies → Labrador

❌ NIE używaj dziedziczenia gdy:

SytuacjaCo zamiast?
Relacja „ma” (has-a)Samochód ma Silnik → kompozycja
Tylko jedna wspólna metodaInterfejs
Chcesz „dziedziczyć” po wieluInterfejsy
Klasy nie są logicznie powiązaneNie twórz sztucznej hierarchii

✅ Dobre przykłady

  • Postac → Wojownik, Mag
  • Pojazd → Samochód, Motocykl
  • Kształt → Koło, Prostokąt
  • Pracownik → Programista, Manager
  • Kontrolka → Button, TextBox

❌ Złe przykłady

  • Samochód → Silnik (ma, nie jest!)
  • Człowiek → Adres (ma adres)
  • Ptak → Samolot (oba latają, ale…)
  • String → StringBuilder (różne cele)
12

Częste błędy

❌ Błąd 1: Brak virtual przy metodzie bazowej

❌ Źle

public void Atakuj() { }

// W klasie pochodnej:
public override void Atakuj()
// BŁĄD! Nie można nadpisać
// metody bez virtual

✅ Dobrze

public virtual void Atakuj() { }

// W klasie pochodnej:
public override void Atakuj()
// OK!

❌ Błąd 2: Brak wywołania base w konstruktorze

❌ Źle

// Klasa bazowa bez konstruktora
// bezparametrowego:
public Postac(string imie) { }

// Klasa pochodna:
public Wojownik(string imie)
{
    // BŁĄD! Brak : base(imie)
}

✅ Dobrze

public Postac(string imie) { }

public Wojownik(string imie) 
    : base(imie)  // Wywołanie base
{
    // OK!
}

❌ Błąd 3: Próba dostępu do private z klasy pochodnej

❌ Źle

// W klasie bazowej:
private int sekret = 42;

// W klasie pochodnej:
int x = sekret;  // BŁĄD!

✅ Dobrze

// W klasie bazowej:
protected int sekret = 42;

// W klasie pochodnej:
int x = sekret;  // OK!

❌ Błąd 4: Dziedziczenie wielokrotne

❌ Źle

public class Syrena : Ryba, Czlowiek
{
    // BŁĄD! C# nie pozwala
}

✅ Dobrze

public class Syrena : Istota
{
    // Jedna klasa bazowa
}
// Użyj interfejsów dla
// wielu "umiejętności"
13

Podsumowanie

Kluczowe pojęcia dziedziczenia
  • Dziedziczenie = klasa przejmuje składowe innej klasy
  • Klasa bazowa = rodzic, nadklasa
  • Klasa pochodna = dziecko, podklasa
  • : = operator dziedziczenia (class Pies : Zwierze)
  • base = odwołanie do klasy bazowej
  • virtual = metoda może być nadpisana
  • override = nadpisuję metodę bazową
  • protected = dostępne dla klasy i jej dzieci
  • Polimorfizm = jedna zmienna, wiele zachowań

Korzyści z dziedziczenia

KorzyśćOpis
DRYDon’t Repeat Yourself – kod wspólny w jednym miejscu
Łatwiejsze zmianyZmiana w klasie bazowej propaguje się do wszystkich
PolimorfizmJeden kod obsługuje wiele typów
RozszerzalnośćŁatwo dodać nową klasę pochodną
CzytelnośćHierarchia odzwierciedla rzeczywistość

Schemat składni

cs/Schemat.cs C#
public class KlasaBazowa
{
    public PoleDostepneWszedzie;
    protected PoleDlaDzieci;
    private PoleTylkoTutaj;
    
    public KlasaBazowa(parametry) { }
    
    public virtual void MetodaDoNadpisania() { }
}

public class KlasaPochodna : KlasaBazowa
{
    public WlasnePole;
    
    public KlasaPochodna(parametry) : base(parametryDoBazowej) { }
    
    public override void MetodaDoNadpisania()
    {
        base.MetodaDoNadpisania();  // opcjonalnie
        // własna implementacja
    }
}
Zadania

Zadania praktyczne

📝 Zadanie 1: Hierarchia pojazdów

Utwórz hierarchię klas pojazdów:

  • Pojazd (bazowa): Marka, Model, RokProdukcji, metoda virtual Jedz()
  • Samochod: LiczbaKol (4), override Jedz() → „Jadę samochodem”
  • Motocykl: LiczbaKol (2), override Jedz() → „Jadę motocyklem”
  • Rower: CzyMaTrzySilnik (bool), override Jedz() → „Jadę rowerem”

Utwórz tablicę Pojazd[] z różnymi pojazdami i wywołaj Jedz() dla każdego.

💡 Podpowiedź: Użyj foreach i zobacz polimorfizm w akcji!

📝 Zadanie 2: System pracowników

Utwórz hierarchię pracowników:

  • Pracownik: Imie, Nazwisko, PensjaBase, metoda virtual ObliczPensje()
  • Programista: JezykProgramowania, override ObliczPensje() = PensjaBase * 1.5
  • Manager: LiczbaPodwladnych, override ObliczPensje() = PensjaBase + (LiczbaPodwladnych * 200)
  • Stażysta: MiesiacStazu, override ObliczPensje() = PensjaBase * 0.5

💡 Podpowiedź: Użyj konstruktorów z base()

📝 Zadanie 3: Kształty geometryczne

Utwórz hierarchię:

  • Ksztalt: Nazwa, Kolor, virtual ObliczPole(), virtual ObliczObwod()
  • Prostokat: Szerokosc, Wysokosc
  • Kolo: Promien (użyj Math.PI)
  • Trojkat: BokA, BokB, BokC, Podstawa, Wysokosc

Dodaj metodę WyswietlInfo() w klasie bazowej używając base.WyswietlInfo() w klasach pochodnych.

💡 Podpowiedź: Pole koła = π * r², obwód = 2 * π * r

⭐ Zadanie 4: Refaktoryzacja kodu

Masz trzy klasy z powtarzającym się kodem. Zrefaktoryzuj je używając dziedziczenia:

cs/DoRefaktoryzacji.cs C#
public class Pies { 
    public string Imie; 
    public int Wiek;
    public void Jedz() { Console.WriteLine("Jem"); }
    public void Szczekaj() { Console.WriteLine("Hau!"); }
}

public class Kot { 
    public string Imie; 
    public int Wiek;
    public void Jedz() { Console.WriteLine("Jem"); }
    public void Miaucz() { Console.WriteLine("Miau!"); }
}

public class Papuga { 
    public string Imie; 
    public int Wiek;
    public void Jedz() { Console.WriteLine("Jem"); }
    public void Mow() { Console.WriteLine("Witaj!"); }
}

💡 Podpowiedź: Utwórz klasę bazową Zwierze z wspólnymi elementami

⭐⭐ Zadanie 5: System kont bankowych

Utwórz rozbudowaną hierarchię:

  • Konto: NumerKonta (readonly), Saldo (protected), Wlasciciel
    • Konstruktor z base
    • virtual Wplac(kwota)
    • virtual Wyplac(kwota) – z walidacją
    • virtual ObliczOdsetki() – domyślnie 0%
  • KontoOszczednosciowe: OprocentowanieRoczne, override ObliczOdsetki()
  • KontoFirmowe: LimitDebetu, override Wyplac() – można wejść w debet
  • KontoStudenckie: DataWaznosci, override Wplac() – bonus 5% do wpłat

💡 Podpowiedź: W Wyplac() dla KontoFirmowe sprawdź Saldo + LimitDebetu >= kwota