Czym jest dziedziczenie?
Dziedziczenie jest to jeden z 4 podstawowych paradygmatów programowania obiektowego. Jest to rodzaj relacji pomiędzy dwoma klasami, która pozwala jednemu z nich dziedziczyć kod drugiego. Dzięki niemu można budować hierarchię między klasami, to znaczy obiekt może przejąć metody i właściwości innego obiektu. Najczęściej używamy dziedziczenia po to, aby stworzyć bardziej wyspecjalizowaną klasę. To znaczy, jeżeli potrzebujemy stworzyć klasę, która ma wiele takich samych właściwości i metod, ale również kilka dodatkowych, to znaczy jest bardziej wyspecjalizowana niż klasa nadrzędna, to w takim przypadku warto zastosować dziedziczenie pomiędzy tymi dwoma klasami. Dziedziczyć możemy tylko po jednej klasie i tylko składowe z odpowiednim modyfikatorem dostępu, ale o tym więcej będzie w kolejnych artykułach.
Nazewnictwo
W połączeniu z dziedziczeniem, często spotkasz się z pojęciami takimi jak typ pochodny, podtyp, które oznaczają typ, który dziedziczy składowe typu bardziej ogólnego. A także pojęciami typ bazowy, nadtyp, to znaczy typ, który jest bardziej ogólny, z którego dziedziczy typ pochodny. To znaczy, jeżeli klasa Car dziedziczy po klasie Vehicle, to klasa Car jest typem pochodnym (podtypem) klasy Vehicle. A klasa Vehicle jest typem bazowym (nadtypem) klasy Car. Każdy samochód jest pojazdem, ale nie każdy pojazd jest samochodem.
Przykład w C#
Jak zauważyłeś w moich poprzednich artykułach, najbardziej lubię tłumaczyć dane pojęcie na przykładzie. Przejdźmy zatem do pierwszego z nich. Mamy taki kod, które przedstawia nam proste dziedziczenie:
public class Vehicle
{
public string LicensePlate { get; set; }
public void Start()
{
Console.WriteLine("Vehicle started.");
}
}
public class Car : Vehicle
{
public void Refuel()
{
Console.WriteLine("Car refuel.");
}
}
public class Program
{
static void Main(string[] args)
{
Car car = new Car();
car.LicensePlate = "ABC 11XZ";
car.Refuel();
car.Start();
}
}
Jak widzisz mamy tutaj 2 klasy. Klasę pojazd oraz klasę samochód. Taki zapis:
public class Car : Vehicle
Oznacza, że klasa Car, dziedziczy po klasie Vehicle. To znaczy, po nazwie klasy dajemy dwukropek, oraz nazwę klasy, z której chcemy dziedziczyć. Także, nasz nadrzędny typ to znaczy Vehicle, ma właściwość LicensePlate oraz metodę Start. Dzięki temu, że klasa Car, dziedziczy po tej klasie, to ma dostęp do tych 2 składników, a także ma 1 swoją dodatkową metodę Refuel. Jak widzisz w metodzie Main w naszym programie, użyliśmy wszystkich tych 3 składowych. Dzięki dziedziczeniu nie musimy definiować tych właściwości i metod ponownie. Nie musimy duplikować kodu. Oczywiście, dziedziczenie nie działa w drugą stronę, to znaczy obiekt klasy Vehicle nie będzie miał dostępu do metody Refuel:
public class Program
{
static void Main(string[] args)
{
Car car = new Car();
car.LicensePlate = "ABC 11XZ";
car.Refuel();
car.Start();
Vehicle vehicle = new Vehicle();
vehicle.LicensePlate = "ABC 22XZ";
vehicle.Refuel();//Błąd kompilacji!!
vehicle.Start();
}
}
Konstruktory
Jeżeli tworzysz nowy obiekt, klasy pochodnej, to zawsze najpierw jest wywoływany konstruktor klasy bazowej. To znaczy:
public class Vehicle
{
public string LicensePlate { get; set; }
public Vehicle()
{
Console.WriteLine("Konstruktor Vehicle.");
}
public void Start()
{
Console.WriteLine("Vehicle started.");
}
}
public class Car : Vehicle
{
public Car()
{
Console.WriteLine("Konstruktor Car.");
}
public void Refuel()
{
Console.WriteLine("Car refuel.");
}
}
public class Program
{
static void Main(string[] args)
{
Car car = new Car();
car.LicensePlate = "ABC 11XZ";
car.Refuel();
car.Start();
}
}
//OUTPUT:
//Konstruktor Vehicle.
//Konstruktor Car.
//Car refuel.
//Vehicle started.
Co więcej, jeżeli w klasie bazowej jest tylko 1 konstruktor i jest on z parametrem, to w klasie pochodnej, musimy za pomocą słowa kluczowej base, przekazać wartość do tego parametru. Jeżeli tego nie zrobimy, to kompilator zgłosi nam błąd:
public class Vehicle
{
public string LicensePlate { get; set; }
public Vehicle(string licensePlate)
{
Console.WriteLine("Konstruktor Vehicle.");
LicensePlate = licensePlate;
}
public void Start()
{
Console.WriteLine("Vehicle started.");
}
}
public class Car : Vehicle
{
public Car() : base("AA 123")
{
Console.WriteLine("Konstruktor Car.");
}
public Car(string licensePlate) : base(licensePlate)
{
Console.WriteLine("Konstruktor Car.");
}
public void Refuel()
{
Console.WriteLine("Car refuel.");
}
}
public class Program
{
static void Main(string[] args)
{
Car car1 = new Car("ABC 11XZ");
car1.Refuel();
car1.Start();
Car car2 = new Car();
car2.Refuel();
car2.Start();
}
}
Klasy zamknięte
Jeżeli chcemy zamknąć klasę na dziedziczenie, to należy wówczas użyć słowa kluczowego sealed. Klasa oznaczona tym atrybutem, nigdy nie będzie klasą nadrzędną dla żadnego innego typu, nie można po niej wtedy dziedziczyć:
public sealed class Vehicle
{
public string LicensePlate { get; set; }
public void Start()
{
Console.WriteLine("Vehicle started.");
}
}
public class Car : Vehicle //Błąd kompilacji!!!
{
public void Refuel()
{
Console.WriteLine("Car refuel.");
}
}
PODSUMOWANIE:
Dziedziczenie jest niezwykle ważnym zagadnieniem, jeżeli chodzi o programowanie obiektowe. Dzisiaj starałem Ci się przekazać najważniejsze informacje związane z dziedziczeniem. W kolejnych artykułach pokaże Ci jeszcze kilka szczegółów, dlatego ważne jest, abyś zrozumiał podstawy, bez tego możesz mieć problem, aby zrozumieć kolejne zagadnienia. Jeżeli masz jeszcze jakieś pytania do tego artykułu, to napisz w komentarzu. Do zobaczenia w kolejnym artykule, gdzie poruszymy temat kolejnego bardzo ważnego zagadnienia związanego z programowaniem obiektowym.
Poprzedni artykuł - Szybkie Wprowadzenie do ASP.NET Core.
Następny artykuł - Co To Jest Polimorfizm w Programowaniu Obiektowym?.