Przejdź do treści stopki
Iron Academy Logo
Naucz się C#
Naucz się C#

Inne Kategorie

Zrozumienie programowania obiektowego w C#

Tim Corey
49m 41s

Dziedziczenie i interfejsy to integralne części programowania obiektowego (OOP). Tim Corey, w swoim wideo "Dziedziczenie vs Interfejsy w C#: Programowanie Obiektowe", zapewnia szczegółowe wyjaśnienie, kiedy używać dziedziczenia, a kiedy wybrać interfejsy.

Ten artykuł służy jako kompleksowy przewodnik po wideo Tima Coreya. Rozkłada kluczowe koncepcje, przykłady i wyjaśnienia kodu zawarte w wideo, podkreślając różnice między dziedziczeniem a interfejsami oraz kiedy używać każdego z nich.

Wprowadzenie

Tim na (0:00) zaczyna od podkreślenia znaczenia rozróżniania między dziedziczeniem a interfejsami. Podkreśla potrzebę zrozumieniuiuiuiuia, kiedy używać każdej z koncepcji, aby osiągnąć najlepsze rezultaty. Jego celem jest zademonstrowanie tego poprzez przykłady, zaczynając od niewłaściwego użycia pojedynczego dziedziczenia, a następnie jego poprawienia.

Tworzenie projektu

Na (1:08), Tim tworzy prostą aplikację konsolową używając .NET 5. Nazywa projekt "OODemoApp" i wyjaśnia, że głównym celem jest zademonstrowanie koncepcji, a nie tworzenie kodu gotowego do produkcji.

Rozumienie Dziedziczenia

Tim na (1:55) zagłębia się w podstawy dziedziczenia. Określa dziedziczenie jako mechanizm, w którym właściwości i metody klasy bazowej są dziedziczone przez klasę pochodną. Podkreśla, że dziedziczenie nie powinno być używane tylko do ponownego użycia i dzielenia się kodem, ale do ustanawiania logicznej relacji typu "jest-a".

Kluczowe punkty:

  • Relacja "jest-a": Zapewnia, że klasa pochodna jest typem klasy bazowej.
  • Wspólna logika: Klasy dziedziczące powinny dzielić się wspólną logiką, a nie tylko właściwościami lub sygnaturami metod.

Przykładowa Klasa: Samochód do wynajęcia

Tim na (7:52) tworzy klasę RentalCar do zilustrowania podstawowej koncepcji dziedziczenia. Ta klasa reprezentuje samochód do wynajęcia w agencji wynajmu w Miami na Florydzie.

public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}
public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}

Demonstrowanie Wad Niewłaściwego Dziedziczenia

Tim na (10:15) wyjaśnia, jak niewłaściwe użycie dziedziczenia może prowadzić do problemów. Podkreśla, że jeśli dziedziczenie jest źle używane, może prowadzić do kodu, który jest trudny do zarządzania i rozwoju. Doradza unikanie używania dziedziczenia tylko do dzielenia się kodem.

Dodawanie Wyliczeń dla Typów Samochodów i Ciężarówek

Tim na (10:45) dodaje wyliczenie dla typów samochodów. Tworzy nowy plik klasy o nazwie Enums.cs, aby przechowywać wszystkie wyliczenia w jednym miejscu. To wyliczenie pomoże różnicować między różnymi stylami samochodów.

// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}
// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}

Następnie dodaje właściwość do klasy RentalCar, aby określić typ samochodu.

public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}
public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}

Rozszerzanie na Ciężarówki

Jak Tim wyjaśnia na (12:27), agencja wynajmu decyduje się dodać ciężarówki do swojej floty, co wprowadza nowe wymagania. Tworzy klasę RentalTruck dziedziczącą z klasy rodzica RentalVehicle.

public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}
public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}

Następnie definiuje nowe wyliczenie dla typów ciężarówek.

// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}
// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}

Obsługa Różnych Typów Właściwości

Tim na (15:28) podkreśla, że tylko dlatego, że dwie właściwości mają tę samą nazwę, nie znaczy, że są tym samym. Ilustruje to na przykładzie właściwości Style, która może oznaczać różne wyliczenia (CarType dla samochodów i TruckType dla ciężarówek).

Wprowadzenie Łodzi do Floty

Agencja wynajmu rozszerza swoją flotę, aby obejmować łodzie. Tim demonstruje, jak poradzić sobie z tym, tworząc klasę RentalBoat. Początkowo wydaje się to wykonalne, ponieważ łodzie mogą dzielić niektóre właściwości z samochodami i ciężarówkami.

public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}
public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}

Zajmowanie się Żaglówkami

Wprowadzenie żaglówek stanowi wyzwanie, ponieważ żaglówki nie mają silników. Tim na (19:57) ilustruje ograniczenia dziedziczenia w tym scenariuszu.

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

Rozwiązywanie Problemów za Pomocą Interfejsów

Tim sugeruje, aby metody StartEngine i StopEngine były wirtualne w klasie bazowej, aby umożliwić ich nadpisanie w klasach pochodnych, które ich nie używają.

public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

Obsługa Metod, Które Nie Powinny Być Wywoływane

Tim na (21:56) wyjaśnia pułapki posiadania metod w klasach dziedziczonych, które nie powinny być wywoływane. Na przykładzie klasy RentalSailboat, która nie ma silnika, dziedziczy metody StartEngine i StopEngine z klasy RentalVehicle. Taka sytuacja może prowadzić do problemów, jeśli te metody zostaną przypadkowo wywołane, ponieważ muszą rzucać wyjątki, aby wskazać, że są nieodpowiednie.

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

Uznanie Ograniczeń Dziedziczenia

Tim na (24:06) podkreśla, jak dziedziczenie może prowadzić do skomplikówanej i zagmatwanej bazy kodu, gdy przestaje mieć to sens logiczny. Na przykład, żaglówka nie powinna być traktowana jako RentalVehicle z silnikiem. To demonstruje ograniczenia dziedziczenia i konieczność lepszego podejścia projektowego.

Przejście na Lepszy Projekt z Interfejsami

Aby rozwiązać te problemy, Tim sugeruje lepszy projekt przy użyciu interfejsów. Zaczyna od stworzenia nowego projektu aplikacji konsolowej o nazwie "BetterOODemo", aby zademonstrować ulepszone podejście.

Definiowanie Interfejsu

Tim wprowadza interfejs IRental, aby ująć właściwości wspólne dla wszystkich wynajmów.

public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}
public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}

Tworzenie Klasy Bazowej dla Pojazdów Lądowych

Tim następnie tworzy klasę bazową dla pojazdów lądowych, oddzielając koncepcję wynajmu pojazdu od samego pojazdu.

public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

Zmieniając nazwę klasy bazowej pojazdu na LandVehicle, Tim zapewnia, że tylko odpowiednie pojazdy dziedziczą metody związane z silnikiem.

Implementacja Klas Samochodów i Ciężarówek

Tim tworzy klasy Car i Truck, które dziedziczą z LandVehicle i implementują interfejs IRental.

public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}
public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}

Ten projekt utrzymuje wyraźny podział zadań, zapewniając, że każda klasa zawiera tylko właściwości i metody istotne dla jej typu.

Unikanie Duplikacji Kodu

Tim na (31:41) omawia znaczenie unikania niepotrzebnej duplikacji kodu. Wyjaśnia, że chociaż interfejs IRental wymaga tych samych właściwości w wielu klasach, nie uważa się tego za naruszenie zasady DRY (Don't Repeat Yourself), ponieważ nie jest duplikówana logika – tylko deklaracje właściwości.

Implementacja Klasy Wynajmu Żaglówek

Tim na (35:09) wyjaśnia, jak oddzielnie obsługiwać klasę RentalSailboat, implementując interfejs IRental, zamiast dziedziczyć z LandVehicle. To podejście pomaga uniknąć pułapek związanych z niewłaściwym dziedziczeniem.

public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}
public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}

Zarządzanie Różnymi Wynajmami

Tim tworzy listę do zarządzania różnymi rodzajami wynajmów, wykorzystując interfejs IRental do przechowywania różnych typów wynajmów, w tym ciężarówek, żaglówek i samochodów.

List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};
List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};

Ten projekt pozwala na przechodzenie przez wynajmy i dostęp do wspólnych właściwości, takich jak CurrentRenter, PricePerDay, i RentalId.

foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}
foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}

Rzutowanie na Specyficzne Typy

Aby uzyskać dostęp do specyficznych właściwości i metod różnych typów wynajmów, Tim demonstruje, jak używać słowa kluczowego is, aby sprawdzić i rzutować obiekty na ich odpowiednie typy.

foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}
foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}

Elastyczność z Interfejsami

Tim podkreśla, że używanie interfejsów zapewnia elastyczność dla przyszłych zmian. Na przykład, dodanie nowych typów wynajmów, takich jak czołgi lub telewizory, nie zakłóci istniejącej struktury.

Unikanie Nadmiernego Przydzielania Dziedziczenia

Tim doradza, aby unikać nadmiernego używania dziedziczenia do dzielenia się kodem, ponieważ może to prowadzić do złożonej i nieelastycznej bazy kodu. Zamiast tego, rekomenduje wykorzystanie interfejsów i kompozycji w celu osiągnięcia pożądanych rezultatów bez rozciągania relacji "jest-a" poza jej logiczne granice.

Wnioski

Wyjaśnienie Tima Coreya dotyczące dziedziczenia i interfejsów w OOP oferuje jasną ścieżkę do tworzenia łatwego w utrzymaniu i elastycznego kodu. Poprzez przedstawienie typowych pułapek i dostarczenie wyrafinowanego projektu z interfejsami, zapewnia, że programiści mogą podejmować świadome decyzje dotyczące efektywnego strukturyzowania ich aplikacji.

Aby dogłębnie poznać te koncepcje i zobaczyć je w działaniu, obejrzyj pełne wideo Tima. Jego kanał to skarbnica poradników programistycznych. Nie przegap!

Hero Worlddot related to Zrozumienie programowania obiektowego w C#
Hero Affiliate related to Zrozumienie programowania obiektowego w C#

Zarabiaj więcej, dzieląc się tym, co kochasz

Tworzysz treści dla deweloperów pracujących z .NET, C#, Java, Python, czy Node.js? Zamień swoją wiedzę specjalistyczną na dodatkowy dochód!

Zespol wsparcia Iron

Jestesmy online 24 godziny, 5 dni w tygodniu.
Czat
Email
Zadzwon do mnie