Dziedziczenie i polimorfizm — różne urządzenia, wspólne polecenie

Drukarka i skaner są urządzeniami, ale inaczej opisują swoje zadania. Wspólny typ pozwoli obsługiwać je w jednej pętli.

C#Technikum
CEL LEKCJI

Czego się dziś nauczysz

  • Utworzysz klasę pochodną.
  • Użyjesz virtual i override.
  • Wyjaśnisz wybór metody podczas działania.

Przygotowanie: lekcje 01–44. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.

TEORIA

Po co ten mechanizm? Kod przed i po

Problem: dwa rodzaje urządzeń kopiują wspólny kod

Drukarka i skaner mają nazwę oraz potrafią przedstawić się użytkownikowi. Zmiana formatu opisu wymaga teraz poprawienia dwóch metod.

Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program uruchamiaj w oddzielnym projekcie konsolowym.

Program.cs
using System;
class Drukarka
{
    public string Nazwa { get; }
    public Drukarka(string nazwa) { Nazwa = nazwa; }
    public string Opis() { return $"Urzadzenie: {Nazwa}"; }
}
class Skaner
{
    public string Nazwa { get; }
    public Skaner(string nazwa) { Nazwa = nazwa; }
    public string Opis() { return $"Urzadzenie: {Nazwa}"; }
}
class Program
{
    static void Main()
    {
        Console.WriteLine(new Drukarka("D1").Opis());
        Console.WriteLine(new Skaner("S1").Opis());
    }
}
Wynik przykładu
Urzadzenie: D1
Urzadzenie: S1

Po: wspólna część w bazie

Dwukropek oznacza dziedziczenie. Drukarka jest Urzadzeniem, więc może korzystać ze wspólnego Opis. Konstruktor pochodny przekazuje nazwę do bazy.

Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program uruchamiaj w oddzielnym projekcie konsolowym.

Program.cs
using System;
class Urzadzenie
{
    public string Nazwa { get; }
    public Urzadzenie(string nazwa) { Nazwa = nazwa; }
    public string Opis() { return $"Urzadzenie: {Nazwa}"; }
}
class Drukarka : Urzadzenie
{
    public Drukarka(string nazwa) : base(nazwa) { }
}
class Skaner : Urzadzenie
{
    public Skaner(string nazwa) : base(nazwa) { }
}
class Program
{
    static void Main()
    {
        Console.WriteLine(new Drukarka("D1").Opis());
        Console.WriteLine(new Skaner("S1").Opis());
    }
}
Wynik przykładu
Urzadzenie: D1
Urzadzenie: S1

1. Wersja przed zmianą

Nazwa i Opis pojawiają się w dwóch niezależnych klasach.

2. Wersja po zmianie

Wspólna właściwość i opis są zadeklarowane raz w Urzadzeniu.

3. Co konkretnie zyskaliśmy?

Zmiana napisu Urzadzenie na Sprzet odbywa się w jednym miejscu. Klasa pochodna może później dodać własne zachowanie. Base(nazwa) przygotowuje bazową część tego samego obiektu; nie powstaje dodatkowe urządzenie.

4. Kiedy ten mechanizm jest zbędny?

Dziedziczenie wiąże klasy. Sama jedna podobna linia kodu nie uzasadnia hierarchii. Jeśli obiekt MA inny obiekt, jak drukarka ma pojemnik z tuszem, stosuj składanie. Gdy typy różnią się tylko danymi, czasem wystarczy jedna klasa i różne obiekty.

Ważne reguły

Klasa ma jedną bezpośrednią klasę bazową. Konstruktorów nie dziedziczy — może je wywołać przez base. Prywatne dane bazy są częścią stanu obiektu, lecz klasa pochodna nie ma do nich bezpośredniego dostępu. Sealed class zamyka możliwość dziedziczenia. Console.WriteLine(string) wypisuje gotowy opis i zwraca void; Opis i konstruktory są naszym kodem.

Polimorfizm: przed użyciem wspólnej operacji

Rozważ funkcję z warunkami: dla rodzaju „roboty” zwraca jeden napis, dla „quiz” drugi. Jeśli podobne rozgałęzienie pojawia się przy uruchomieniu, zatrzymaniu i opisie, dodanie atrakcji wymaga zmian w wielu miejscach.

Kompletny, osobny plik Program.cs. Przed uruchomieniem przewidź wynik. Każdy program uruchamiaj w oddzielnym projekcie konsolowym.

Program.cs
using System;
class Program
{
    static string Uruchom(string rodzaj)
    {
        if (rodzaj == "roboty") return "Roboty ruszaja";
        if (rodzaj == "quiz") return "Pytanie na ekranie";
        return "Otwarcie stanowiska";
    }
    static void Main()
    {
        string[] rodzaje = { "roboty", "quiz", "zwykla" };
        foreach (string rodzaj in rodzaje) Console.WriteLine(Uruchom(rodzaj));
    }
}
Wynik przykładu
Roboty ruszaja
Pytanie na ekranie
Otwarcie stanowiska

Po: przejdź do osobnej lekcji o polimorfizmie

Wersja po zmianie daje identyczny wynik, ale umieszcza zachowania w klasach Roboty i Quiz. Pętla wywołuje Uruchom bez sprawdzania rodzaju. To konkretna korzyść override. Nie skraca to koniecznie kodu — zmienia miejsce, w którym dodajesz nowe zachowanie.

TEORIA

Wyjaśnienie krok po kroku

Relacja „jest rodzajem”

Drukarka : Urzadzenie oznacza, że Drukarka dziedziczy po Urzadzenie. Klasa w C# może bezpośrednio dziedziczyć po jednej klasie. Dziedziczenie nie jest dobrym modelem dla relacji „ma”: drukarka ma toner, ale nie jest tonerem.

Tak wygląda to w kodzie — Definicje typów — poza Main. To fragment ilustracyjny, nie cały Program.cs.

Fragment C#
class Urzadzenie { }
class Drukarka : Urzadzenie { }

Drukarka jest rodzajem Urzadzenia. Dwukropek wskazuje klasę bazową; nie oznacza pola przechowującego drugie urządzenie.

Virtual i override

virtual w klasie bazowej zezwala na zmianę zachowania w klasie pochodnej. override podaje nową wersję. Przy wywołaniu przez zmienną typu bazowego zostanie wybrana wersja zgodna z rzeczywistym typem obiektu.

Tak wygląda to w kodzie — Definicje typów — poza Main. To fragment ilustracyjny, nie cały Program.cs.

Fragment C#
class Urzadzenie
{
    public virtual string Start() { return "Start"; }
}
class Drukarka : Urzadzenie
{
    public override string Start() { return "Drukowanie"; }
}

Virtual udostępnia wersję domyślną. Override określa zachowanie drukarki przy wywołaniu tej samej operacji.

Abstract i kompozycja

Klasa abstract nie pozwala tworzyć bezpośrednich obiektów przez new. Metoda abstract nie ma ciała i musi zostać zaimplementowana w konkretnej klasie pochodnej. Jeśli klasy mają tylko współpracować, często prostsze jest przechowywanie jednego obiektu jako właściwości drugiego.

Tak wygląda to w kodzie — Definicje typów — poza Main. To fragment ilustracyjny, nie cały Program.cs.

Fragment C#
class Bateria { }
class Robot
{
    private Bateria bateria = new Bateria();
}

Robot MA baterię, dlatego przechowuje ją w polu. Zapis Robot : Bateria oznaczałby inną relację: „robot jest baterią”.

PRZYKŁAD

Przykład do zbadania

Wklej cały przykład do nowego projektu konsolowego jako Program.cs. Zastąp poprzednią zawartość pliku. Nie łącz kilku całych programów w jednym pliku.

Program.cs — cały plik
using System;
class Urzadzenie
{
    public virtual string Opis() { return "Urzadzenie"; }
}
class Drukarka : Urzadzenie
{
    public override string Opis() { return "Drukuje dokument"; }
}
class Skaner : Urzadzenie
{
    public override string Opis() { return "Skanuje dokument"; }
}
class Program
{
    static void Main()
    {
        Urzadzenie[] sprzet = { new Drukarka(), new Skaner() };
        foreach (Urzadzenie element in sprzet) Console.WriteLine(element.Opis());
    }
}
Oczekiwany wynik
Drukuje dokument
Skanuje dokument

Co dzieje się po kolei

  • Tablica przechowuje referencje wspólnego typu Urzadzenie.
  • Pętla nie pyta, czy element jest drukarką; wywołuje wspólną metodę.
  • override sprawia, że wykonanie zależy od obiektu, choć zmienna ma typ bazowy.

Pobierz Program.cs

ELEMENTY WBUDOWANE

Użyte w tej lekcji elementy

Metody .NET są gotowymi narzędziami biblioteki. Instrukcje języka i nasze metody oznaczamy osobno. Właściwości, np. Length, odczytujemy bez nawiasów.

Element Wynik Działanie i ograniczenia
element.Opis() string Nasza metoda wirtualna zwracająca opis działania.
Console.WriteLine(tekst) void Wyświetla wynik zwrócony przez Opis.
CZĘSTE BŁĘDY

Częste błędy

  • Samo powtórzenie nazwy metody nie jest poprawnym zastępstwem override.
  • Dziedziczenie nie daje dostępu do prywatnych elementów klasy bazowej.
  • Nie twórz hierarchii tylko po to, żeby uniknąć dwóch powtórzonych linii.
ZADANIA

Zadania

ZAD 1Nowy sprzęt★☆☆

Dodaj Projektor dziedziczący po Urzadzenie i zwracający Wyświetla obraz.

Sprawdź: Po dopisaniu obiektu tablica wyświetla trzy opisy.

ZAD 2Typ zmiennej★★☆

Zapisz Urzadzenie u = new Drukarka(); i wywołaj Opis.

Sprawdź: Wynik: Drukuje dokument.

ZAD 3Projekt modelu★★★

Ustal relację między Samochod, Pojazd i Silnik.

Sprawdź: Samochod jest Pojazdem i ma Silnik.

PODSUMOWANIE

Sprawdź, czy rozumiesz

Wyjaśnij własnymi słowami, jak działa główny przykład i dlaczego daje taki wynik. Następnie zmień jedną daną i przewidź wynik przed uruchomieniem. Wróć do celów lekcji i sprawdź, które potrafisz już wykonać samodzielnie.

Dokumentacja: Microsoft Learn — temat tej lekcji.