07
Abstrakcja danych: klasy i metody abstrakcyjne
Cel zadania

Zrozumienie koncepcji klas abstrakcyjnych jako niekompletnych szablonów oraz metod abstrakcyjnych jako wymuszenia implementacji w klasach pochodnych. Student dowie się, dlaczego nie można tworzyć instancji klas abstrakcyjnych i jaka jest różnica między metodą wirtualną (opcjonalną) a abstrakcyjną (obowiązkową).

Scenariusz problemowy

W miarę rozwoju systemu dla komisu "Auto-Premium" dostrzegasz pewną nielogiczność: na placu nigdy nie pojawia się po prostu bliżej nieokreślony "Pojazd". Zawsze jest to albo konkretny "Samochod", albo konkretny "Motocykl". Sam obiekt typu "Pojazd" jest zbyt ogólny, by mógł istnieć jako realny byt, ale jest niezbędny jako wspólny fundament dla wszystkich innych typów maszyn. Decydujesz się więc na przekształcenie klasy bazowej w klasę abstrakcyjną (abstract class).

Dzięki temu wprowadzasz "bezpiecznik" – nikt omyłkowo nie stworzy obiektu samej bazy. Jednocześnie, jako zarządca komisu, musisz zadbać o finanse. Wprowadzasz obowiązek zaimplementowania metody "ObliczPodatekDrogowy()". Logika tego wyliczenia jest skrajnie różna: dla samochodów zależy od emisji spalin, a dla motocykli od pojemności silnika. Robiąc metodę abstrakcyjną (abstract method), wymuszasz na każdym programiście dopisującym nowy typ pojazdu (np. Ciężarówka), aby samodzielnie zdefiniował tę logikę. Jeśli tego nie zrobi, program się nie skompiluje. To potężne narzędzie do utrzymania porządku i narzucania reguł w dużych projektach IT.

Opis wykonania
  • Dodaj słowo kluczowe abstract przy deklaracji klasy Pojazd.
  • Wewnątrz klasy Pojazd zdefiniuj właściwości Cena oraz Marka.
  • Dodaj metodę abstrakcyjną: public abstract double ObliczPodatek(); (zauważ brak ciała metody i średnik na końcu).
  • Zdefiniuj metodę wirtualną Uruchom() – z domyślnym komunikatem startowym.
  • Stwórz klasę Samochod dziedziczącą po Pojazd. Nadpisz metodę ObliczPodatek (np. 10% ceny).
  • Stwórz klasę Motocykl. Nadpisz metodę ObliczPodatek (np. stała opłata 200 PLN).
  • W metodzie Main spróbuj utworzyć obiekt klasy Pojazd: new Pojazd(...) i zaobserwuj błąd kompilacji.
  • Utwórz listę List<Pojazd> i wypełnij ją instancjami klas pochodnych.
  • W pętli foreach wywołaj metody ObliczPodatek() oraz Uruchom() dla każdego elementu listy.
  • Wyjaśnij w komentarzu, że klasa abstrakcyjna może posiadać zwykłe metody, ale metoda abstrakcyjna może istnieć tylko w klasie abstrakcyjnej.
Kod źródłowy
Program.cs
using System;
using System.Collections.Generic;

namespace LaboratoriumOOP
{
    // KLASA ABSTRAKCYJNA: Nie pozwala na tworzenie własnych instancji.
    // Jest jedynie wzorcem/kontraktem dla innych klas.
    public abstract class Pojazd
    {
        public string Marka { get; set; }
        public double Cena { get; set; }

        protected Pojazd(string marka, double cena)
        {
            Marka = marka;
            Cena = cena;
        }

        // METODA ABSTRAKCYJNA: Brak ciała (implementacji).
        // Każda klasa pochodna MUSI ją zaimplementować (override).
        public abstract double ObliczPodatek();

        // METODA WIRTUALNA: Posiada ciało, podklasy MOGĄ ją zmienić.
        public virtual void Uruchom()
        {
            Console.WriteLine($"Systemy pojazdu {Marka} zostały uruchomione.");
        }
    }

    public class Samochod : Pojazd
    {
        public Samochod(string marka, double cena) : base(marka, cena) { }

        // Obowiązkowa implementacja kontraktu
        public override double ObliczPodatek()
        {
            return Cena * 0.12; // Podatek 12% od ceny
        }

        public override void Uruchom()
        {
            base.Uruchom();
            Console.WriteLine(" -> Włączono wycieraczki i światła drogowe.");
        }
    }

    public class Motocykl : Pojazd
    {
        public Motocykl(string marka, double cena) : base(marka, cena) { }

        // Obowiązkowa implementacja kontraktu
        public override double ObliczPodatek()
        {
            return 250.0; // Stała stawka podatku dla motocykli
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("--- ZADANIE 07: Abstrakcja i klasy bazowe ---\n");

            // BŁĄD KOMPILACJI - nie można utworzyć obiektu typu abstrakcyjnego:
            // Pojazd p = new Pojazd("Coś", 100); 

            List katalog = new List
            {
                new Samochod("Audi", 50000),
                new Motocykl("Kawasaki", 15000),
                new Samochod("Fiat", 10000)
            };

            foreach (var item in katalog)
            {
                Console.WriteLine($"Pojazd: {item.Marka}, Cena: {item.Cena} PLN");
                Console.WriteLine($"Należny podatek: {item.ObliczPodatek()} PLN");
                item.Uruchom();
                Console.WriteLine("------------------------------------------");
            }

            Console.WriteLine("\nWNIOSEK: Abstrakcja pozwala wymuszać spójne zachowanie ");
            Console.WriteLine("we wszystkich klasach pochodnych.");
            Console.ReadKey();
        }
    }
}
                    
Wynik w konsoli
--- ZADANIE 07: Abstrakcja i klasy bazowe --- Pojazd: Audi, Cena: 50000 PLN Należny podatek: 6000 PLN Systemy pojazdu Audi zostały uruchomione. -> Włączono wycieraczki i światła drogowe. ------------------------------------------ Pojazd: Kawasaki, Cena: 15000 PLN Należny podatek: 250 PLN Systemy pojazdu Kawasaki zostały uruchomione. ------------------------------------------ Pojazd: Fiat, Cena: 10000 PLN Należny podatek: 1200 PLN Systemy pojazdu Fiat zostały uruchomione. -> Włączono wycieraczki i światła drogowe. ------------------------------------------ WNIOSEK: Abstrakcja pozwala wymuszać spójne zachowanie we wszystkich klasach pochodnych.