05
Dziedziczenie i modyfikatory dostępu w hierarchii klas
Cel zadania

Nauka tworzenia hierarchii klas przy użyciu dziedziczenia oraz zrozumienie roli modyfikatora dostępu protected. Student dowie się, jak wywoływać konstruktor bazowy, tworzyć klasy statyczne oraz jak korzystać z klas zagnieżdżonych i przestrzeni nazw.

Scenariusz problemowy

Firma "Auto-Premium" dynamicznie się rozwija i wprowadza do oferty nie tylko samochody, ale również motocykle. Zauważasz, że oba te typy pojazdów mają wiele wspólnych cech, takich jak marka czy rok produkcji, ale różnią się też unikalnymi parametrami – samochody mają liczbę drzwi, a motocykle mogą posiadać wózek boczny. To idealny moment na wprowadzenie techniki dziedziczenia, aby nie powielać kodu i zachować porządek w systemie.

Projektujesz ogólną klasę bazową "Pojazd", która staje się fundamentem dla bardziej wyspecjalizowanych klas "Samochod" i "Motocykl". Wykorzystujesz modyfikator "protected", aby Twoje "dzieci" (klasy pochodne) miały swobodny dostęp do kluczowych pól rodzica, jednocześnie chroniąc te dane przed dostępem z zewnątrz (np. bezpośrednio z poziomu metody Main). Aby mieć pełny wgląd w skalę biznesu, wprowadzasz statyczny licznik, który automatycznie zlicza każdy nowy pojazd trafiający do bazy. Dzięki strukturze przestrzeni nazw (namespaces), Twój kod staje się profesjonalnie zorganizowany i gotowy na dalszą rozbudowę.

Opis wykonania
  • Stwórz nową przestrzeń nazw LaboratoriumOOP.Modele i umieść w niej klasy pojazdów.
  • Zdefiniuj klasę bazową Pojazd z polami protected string Marka oraz protected int Rok.
  • Dodaj do klasy bazowej konstruktor przyjmujący oba parametry.
  • Stwórz klasę pochodną Samochod (dziedziczącą po Pojazd) z unikalnym polem LiczbaDrzwi.
  • Stwórz klasę pochodną Motocykl z polem bool CzyMaWozek.
  • W konstruktorach klas pochodnych użyj słowa kluczowego base(marka, rok), aby przekazać dane do konstruktora rodzica.
  • Zaimplementuj statyczne pole LicznikPojazdow w klasie Pojazd, które zwiększa się przy każdej nowej instancji.
  • Stwórz publiczną, statyczną metodę WyswietlStanKomisu(), która wypisze aktualną liczbę wszystkich pojazdów.
  • W metodzie Main utwórz listę typu List<Pojazd> i dodaj do niej zarówno samochody, jak i motocykle (demonstracja rzutowania w górę - upcasting).
  • Spróbuj w komentarzu pokazać, że próba dostępu do pola Marka (protected) z poziomu Main kończy się błędem kompilacji.
Kod źródłowy
Program.cs
using System;
using System.Collections.Generic;
// Używamy własnej przestrzeni nazw
using LaboratoriumOOP.Modele;

namespace LaboratoriumOOP.Modele
{
    // KLASA BAZOWA: Fundament hierarchii
    public class Pojazd
    {
        // protected: widoczne tylko w tej klasie i klasach pochodnych
        protected string Marka;
        protected int Rok;

        // static: pole współdzielone przez wszystkie instancje (licznik globalny)
        private static int _licznikPojazdow = 0;

        public Pojazd(string marka, int rok)
        {
            Marka = marka;
            Rok = rok;
            _licznikPojazdow++;
            Console.WriteLine($"[INFO] Dodano nowy pojazd do bazy: {marka}.");
        }

        public static void WyswietlStanKomisu()
        {
            Console.WriteLine($"\n--- STAN KOMISU: {_licznikPojazdow} pojazdów ---");
        }

        public virtual void WyswietlInfo()
        {
            Console.Write($"Pojazd: {Marka} ({Rok})");
        }
    }

    // DZIEDZICZENIE: Samochod "jest" (IS-A) Pojazdem
    public class Samochod : Pojazd
    {
        public int LiczbaDrzwi { get; set; }

        // base: wywołanie konstruktora klasy bazowej
        public Samochod(string marka, int rok, int drzwi) : base(marka, rok)
        {
            LiczbaDrzwi = drzwi;
        }

        public override void WyswietlInfo()
        {
            base.WyswietlInfo();
            Console.WriteLine($" [Samochód, Drzwi: {LiczbaDrzwi}]");
        }
    }

    // SEALED: zapobiega dalszemu dziedziczeniu po tej klasie
    public sealed class Motocykl : Pojazd
    {
        public bool CzyMaWozek { get; set; }

        public Motocykl(string marka, int rok, bool wozek) : base(marka, rok)
        {
            CzyMaWozek = wozek;
        }

        public override void WyswietlInfo()
        {
            base.WyswietlInfo();
            string wozekInfo = CzyMaWozek ? "Tak" : "Nie";
            Console.WriteLine($" [Motocykl, Wózek boczny: {wozekInfo}]");
        }
    }
}

namespace LaboratoriumOOP
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("--- ZADANIE 05: Dziedziczenie i statyczne elementy ---\n");

            // UPCASTING: Możemy przechowywać różne pojazdy w jednej liście typu Pojazd
            List flota = new List();

            flota.Add(new Samochod("Volvo", 2020, 5));
            flota.Add(new Motocykl("Yamaha", 2022, false));
            flota.Add(new Samochod("Tesla", 2024, 4));

            // Próba dostępu:
            // Console.WriteLine(flota[0].Marka); // BŁĄD KOMPILACJI - Marka jest protected!

            Console.WriteLine("\nZawartość floty:");
            foreach (var p in flota)
            {
                p.WyswietlInfo(); // Polimorficzne wywołanie metody
            }

            // Dostęp do metody statycznej przez nazwę klasy, nie przez obiekt
            Pojazd.WyswietlStanKomisu();

            Console.WriteLine("\nZakończono. Naciśnij klawisz...");
            Console.ReadKey();
        }
    }
}
                    
Wynik w konsoli
--- ZADANIE 05: Dziedziczenie i statyczne elementy --- [INFO] Dodano nowy pojazd do bazy: Volvo. [INFO] Dodano nowy pojazd do bazy: Yamaha. [INFO] Dodano nowy pojazd do bazy: Tesla. Zawartość floty: Pojazd: Volvo (2020) [Samochód, Drzwi: 5] Pojazd: Yamaha (2022) [Motocykl, Wózek boczny: Nie] Pojazd: Tesla (2024) [Samochód, Drzwi: 4] --- STAN KOMISU: 3 pojazdów --- Zakończono. Naciśnij klawisz...