cs

Hermetyzacja, dziedziczenie i kompozycja w języku C#.

Dziedziczenie

Dziedziczenie wyraża relację "jest" (ang. is a).

public class Publication { }  
public class Book : Publication { }  // Book "jest" publikacją
public class Magazine : Publication { }  // Magazine "jest" publikacją

Interfejsy wyraża relacja "może zrobić" (can do - pozwalają określić funkcjonalność, bez wymuszania dziedziczenia.)

public interface IPrintable
{
    void Print();
}

public class Book : IPrintable
{
    public void Print() => Console.WriteLine("Drukowanie książki...");
}

Zapoznaj się z artykułem a następnie opisz czym jest dziedziczenie:

Wymień elementy niedziedziczone przez klasy pochodne oraz je scharakteryzuj (kiedy są wywoływane, do czego służą szczególnie konstruktory startczne):

W języku C# klasa może dziedziczyć po klasie/klasach oraz implementować interfejsów.

Hermetyzacja (enkapsulacja) w kontekście dziedziczenia

Podaj przykłady kodu obrazujące zakres działania modyfikatorów dostępu.

private -

protected -

internal -

public -

Wymień metody/pola niejawnie dziedziczone z typu Object:

  1. -

  2. -

  3. -

  4. -

  5. -

  6. -

  7. -

  8. -

class zawsze dziecziczy po Object jednak istnieją również inne typy które dziedziczą niejawnie po konkretnych klasach. Wymień klasy po których dziedziczą następujące typy:

struct -

enum - delegate -

Składnia

Dziedziczenie w C# to mechanizm, który pozwala tworzyć nowe klasy na podstawie już istniejących. Klasa pochodna (Pochodna) może dziedziczyć po klasie bazowej (Bazowa), co oznacza, że automatycznie przejmuje jej pola i metody. Aby wskazać dziedziczenie, używa się dwukropka (:), np. class Pochodna : Bazowa { }. Dzięki temu można rozszerzać funkcjonalność klasy bazowej bez konieczności przepisywania kodu.

Jeśli klasa bazowa posiada konstruktor, klasa pochodna może go wywołać, używając słowa kluczowego base(...). Jest to szczególnie przydatne, gdy klasa bazowa wymaga przekazania parametrów podczas tworzenia obiektu. Wywołanie konstruktora klasy bazowej odbywa się w sekcji inicjalizacyjnej konstruktora klasy pochodnej, np.: public Pochodna(string nazwa) : base(nazwa) { }.

Metody w klasach mogą być oznaczone jako virtual, co oznacza, że mogą być nadpisane w klasie pochodnej. Dzięki temu można zmienić działanie metody bazowej, dostosowując je do specyficznych potrzeb klasy pochodnej. Aby to zrobić, w klasie pochodnej należy użyć słowa kluczowego override, np.: public override void PrzedstawSie()

Jeśli klasa bazowa ma charakter ogólny i nie powinna być bezpośrednio instancjonowana, można oznaczyć ją jako abstract. Klasa abstrakcyjna (abstract class) może zawierać metody abstrakcyjne, które nie mają implementacji – ich treść musi być określona w klasach pochodnych. To wymusza na programiście zdefiniowanie konkretnej logiki w klasie dziedziczące np. public abstract void SpecjalnaFunkcja();

using System;

// Klasa abstrakcyjna - nie można tworzyć jej instancji
public abstract class Bazowa
{
    public string Nazwa { get; }  // Właściwość tylko do odczytu

    // Konstruktor klasy bazowej
    public Bazowa(string nazwa)
    {
        Nazwa = nazwa;
        Console.WriteLine("Konstruktor klasy Bazowa");
    }

    // Metoda wirtualna, którą można nadpisać w klasie pochodnej
    public virtual void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Nazwa}, klasa bazowa.");
    }

    // Metoda abstrakcyjna – musi być zaimplementowana w klasie pochodnej
    public abstract void SpecjalnaFunkcja();
}

// Klasa pochodna dziedziczy po klasie Bazowa
public class Pochodna : Bazowa
{
    public string Typ { get; }

    // Konstruktor klasy pochodnej wywołuje konstruktor klasy bazowej
    public Pochodna(string nazwa, string typ) : base(nazwa)
    {
        Typ = typ;
        Console.WriteLine("Konstruktor klasy Pochodna");
    }

    // Nadpisanie metody wirtualnej z klasy bazowej
    public override void PrzedstawSie()
    {
        Console.WriteLine($"Jestem {Nazwa}, klasa pochodna typu {Typ}.");
    }

    // Implementacja metody abstrakcyjnej
    public override void SpecjalnaFunkcja()
    {
        Console.WriteLine($"Specjalna funkcja dla {Typ}!");
    }
}

class Program
{
    static void Main()
    {
        // Tworzenie obiektu klasy pochodnej
        Pochodna obiekt = new Pochodna("Obiekt1", "SpecjalnyTyp");

        // Wywołanie metod
        obiekt.PrzedstawSie();  // Nadpisana metoda
        obiekt.SpecjalnaFunkcja();  // Implementacja metody abstrakcyjnej
    }
}

Zadania

Zadanie 1

Zaprojektuj klasę bazową Employee, która będzie reprezentować ogólnego pracownika. Klasa ta powinna zawierać właściwości: Name (string) - imię pracownika oraz Salary (decimal) - podstawową pensję pracownika. Następnie dodaj konstruktor, który przyjmuje jako parametry imię i pensję, i inicjalizuje odpowiednie właściwości. W klasie tej powinna znaleźć się także metoda DisplayInfo(), która wyświetli informacje o pracowniku (imię i pensję) w konsoli. Kolejnym elementem jest metoda wirtualna CalculateBonus(), która oblicza bonus pracownika. Domyślnie może ona zwracać 0, ale będzie nadpisywana w klasach pochodnych.

Zadanie 2

Stworzyć dwie klasy pochodne, które będą dziedziczyć po klasie Employee. Pierwsza z nich, Manager, powinna posiadać dodatkową właściwość TeamSize (int), która będzie przechowywać liczbę osób w zespole, którym zarządza menedżer. W klasie tej należy nadpisać metodę CalculateBonus(), aby menedżer otrzymywał 20% swojej pensji jako bonus. Dodatkowo należy nadpisać metodę DisplayInfo(), aby oprócz imienia i pensji wyświetlała także liczbę osób w zespole menedżera. Druga klasa, Developer, powinna zawierać właściwość ProgrammingLanguage (string), która będzie przechowywać główny język programowania, w którym specjalizuje się programista. W tej klasie również należy nadpisać metodę CalculateBonus(), aby programista otrzymywał 10% swojej pensji jako bonus. Metodę DisplayInfo() także należy nadpisać, aby wyświetlała informacje o języku programowania.

Zadanie 3

Aby lepiej zorganizować kod, przekształć klasę Employee w klasę pochodną, dziedziczącą po klasie abstrakcyjnej Person. Klasa Person powinna zawierać właściwość Name (string) - imię osoby, konstruktor inicjujący to imię oraz metodę abstrakcyjną DisplayInfo(), którą każda klasa pochodna musi zaimplementować. Klasa Employee powinna teraz dziedziczyć po klasie Person i implementować metodę DisplayInfo().

Zadanie 4

W metodzie Main() utwórz obiekty obu klas pochodnych (Manager i Developer) i wykonaj następujące operacje. Wywołaj metodę DisplayInfo() dla każdego z pracowników, aby wyświetlić pełne informacje (imię, pensja, dodatkowe dane specyficzne dla danego typu pracownika). Wywołaj metodę CalculateBonus(), aby obliczyć i wyświetlić bonus dla każdego z pracowników.

W klasie pochodnej, aby wywołać konstruktor klasy bazowej, użyj słowa kluczowego base(...). Używaj modyfikatorów virtual i override, aby nadpisać metody w klasach pochodnych. Klasa abstrakcyjna Person wymusza zaimplementowanie metod, które nie mają domyślnych implementacji. Dobrze przemyśl, w jaki sposób struktura klas abstrakcyjnych i pochodnych wpływa na rozszerzalność Twojego programu.