Zum Fußzeileninhalt springen
Iron Academy Logo
Lernen Sie C#
Lernen Sie C#

Andere Kategorien

Verstehen von C# OOP

Tim Corey
49m 41s

Vererbung und Schnittstellen sind integrale Bestandteile der objektorientierten Programmierung (OOP). Tim Corey, in seinem Video "Vererbung vs. Schnittstellen in C#: Object Oriented Programming" ausführlich, wann man Vererbung und wann man Schnittstellen verwenden sollte.

Dieser Artikel dient als umfassender Leitfaden für das Video von Tim Corey. Sie schlüsselt die wichtigsten Konzepte, Beispiele und Code-Erklärungen aus dem Video auf und hebt die Unterschiede zwischen Vererbung und Schnittstellen hervor und erklärt, wann beide verwendet werden sollten.

Einführung

Tim (0:00) beginnt damit, die Bedeutung der Unterscheidung zwischen Vererbung und Schnittstellen hervorzuheben. Er betont, dass es wichtig ist, zu verstehen, wann man welches Konzept verwendet, um die besten Ergebnisse zu erzielen. Sein Ziel ist es, dies anhand von Beispielen zu demonstrieren, wobei er mit der falschen Verwendung von Einfachvererbung beginnt und diese dann korrigiert.

Erstellung des Projekts

Bei (1:08) erstellt Tim eine einfache Konsolenanwendung mit .NET 5. Er nennt das Projekt "OODemoApp" und erklärt, dass das primäre Ziel darin besteht, die Konzepte zu demonstrieren, anstatt produktionsreifen Code zu erstellen.

Verständnis der Vererbung

Tim geht bei (1:55) auf die Grundlagen der Vererbung ein. Er definiert Vererbung als einen Mechanismus, bei dem die Eigenschaften und Methoden einer Basisklasse an eine abgeleitete Klasse vererbt werden. Er betont, dass Vererbung nicht nur für die Wiederverwendung und gemeinsame Nutzung von Code verwendet werden sollte, sondern um eine logische "ist-a"-Beziehung herzustellen.

Wichtige Punkte:

  • Is-a-Beziehung: Stellt sicher, dass die abgeleitete Klasse ein Typ der Basisklasse ist.
  • Gemeinsame Logik: Vererbte Klassen sollten eine gemeinsame Logik haben, nicht nur Eigenschaften oder Methodensignaturen.

Beispiel Klasse: Rental Car

Tim (7:52) erstellt eine RentalCar-Klasse, um das grundlegende Konzept der Vererbung zu veranschaulichen. Diese Klasse stellt einen Mietwagen in einer Autovermietung in Miami, Florida, dar.

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");
    }
}

Demonstration der Fallstricke einer fehlerhaften Vererbung

Tim erklärt bei (10:15), wie die unsachgemäße Verwendung von Vererbung zu Problemen führen kann. Er hebt hervor, dass Vererbung, wenn sie missbraucht wird, zu Code führen kann, der schwer zu verwalten und zu erweitern ist. Er rät davon ab, Vererbung nur zum Austausch von Code zu verwenden.

Hinzufügen von Enums für Auto- und LKW-Typen

Tim bei (10:45) fügt eine Aufzählung für Autotypen hinzu. Er erstellt eine neue Klassendatei namens Enums.cs, um alle Aufzählungen an einem Ort zu speichern. Diese Aufzählung hilft bei der Unterscheidung zwischen verschiedenen Autostilen.

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

Anschließend fügt er der Klasse RentalCar eine Eigenschaft hinzu, um den Fahrzeugtyp anzugeben.

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
}

Erweiterung um Trucks

Wie Tim bei (12:27) erklärt, beschließt die Vermietungsagentur, ihre Flotte um LKWs zu erweitern, was neue Anforderungen mit sich bringt. Er erstellt eine RentalTruck-Klasse, die von der übergeordneten Klasse RentalVehicle erbt.

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
}

Anschließend definiert er ein neues Enum für LKW-Typen.

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

Behandlung verschiedener Eigenschaftstypen

Tim (15:28) betont, dass zwei Eigenschaften, nur weil sie denselben Namen haben, nicht gleichbedeutend sind. Er veranschaulicht dies anhand der Eigenschaft Style, die verschiedene Enums (CarType für Autos und TruckType für Lastwagen) bedeuten könnte.

Einführung von Booten in die Flotte

Die Vermietungsagentur erweitert ihre Flotte um Boote. Tim demonstriert, wie man das macht, indem er eine RentalBoat-Klasse erstellt. Auf den ersten Blick scheint es überschaubar, da Boote einige Eigenschaften mit Autos und Lastwagen gemeinsam haben.

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

Der Umgang mit Segelbooten

Die Einführung von Segelbooten stellt eine Herausforderung dar, da Segelboote keine Motoren haben. Tim (19:57) veranschaulicht die Grenzen der Vererbung in diesem Szenario.

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");
    }
}

Lösung des Problems mit Schnittstellen

Tim schlägt vor, die Methoden StartEngine und StopEngine in der Basisklasse virtuell zu machen, damit sie in abgeleiteten Klassen, die diese Methoden nicht verwenden, überschrieben werden können.

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");
    }
}

Behandlung von Methoden, die nicht aufgerufen werden sollten

Tim erklärt bei (21:56) die Fallstricke von Methoden in geerbten Klassen, die nicht aufgerufen werden sollten. Für das Beispiel der Klasse RentalSailboat, die keinen Motor hat, erbt sie die Methoden StartEngine und StopEngine von der Klasse RentalVehicle. Diese Situation kann zu Problemen führen, wenn diese Methoden ungewollt aufgerufen werden, da sie Ausnahmen auslösen müssen, um anzuzeigen, dass sie nicht anwendbar sind.

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");
    }
}

Erkennen der Grenzen der Vererbung

Tim (24:06) betont, wie Vererbung zu einer verworrenen und unübersichtlichen Codebasis führen kann, wenn sie keinen logischen Sinn mehr ergibt. Zum Beispiel sollte ein Segelboot nicht als RentalVehicle mit Motor behandelt werden. Dies zeigt die Grenzen der Vererbung und die Notwendigkeit eines besseren Designansatzes.

Bessere Gestaltung mit Schnittstellen

Um diese Probleme zu lösen, schlägt Tim ein besseres Design mit Schnittstellen vor. Er beginnt mit der Erstellung eines neuen Konsolenanwendungsprojekts namens "BetterOODemo", um den verbesserten Ansatz zu demonstrieren.

Definieren der Schnittstelle

Tim führt die IRental-Schnittstelle ein, um Eigenschaften zu kapseln, die allen Mietverträgen gemeinsam sind.

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; }
}

Erstellung der Basisklasse für Landfahrzeuge

Anschließend erstellt Tim eine Basisklasse für Landfahrzeuge, die das Konzept der Fahrzeugvermietung vom Fahrzeug selbst trennt.

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");
    }
}

Durch die Umbenennung der Basisfahrzeugklasse in LandVehicle stellt Tim sicher, dass nur geeignete Fahrzeuge die motorbezogenen Methoden erben.

Implementierung von Auto- und LKW-Klassen

Tim erstellt die Klassen Car und Truck, die von LandVehicle erben und die Schnittstelle IRental implementieren.

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; }
}

Dieses Design sorgt für eine klare Trennung der Bereiche und stellt sicher, dass jede Klasse nur Eigenschaften und Methoden hat, die für ihren Typ relevant sind.

Vermeidung von Code-Duplikationen

Tim erörtert bei (31:41), wie wichtig es ist, unnötige Code-Duplikationen zu vermeiden. Er erklärt, dass die IRental-Schnittstelle zwar dieselben Eigenschaften in mehreren Klassen erfordert, dies aber nicht als Verstoß gegen das DRY-Prinzip (Don't Repeat Yourself) angesehen wird, da keine Logik dupliziert wird - nur Eigenschaftsdeklarationen.

Implementierung der Klasse der Mietsegelboote

Tim (35:09) erklärt, wie man die Klasse RentalSailboat separat behandeln kann, indem man die Schnittstelle IRental implementiert, anstatt von LandVehicle zu erben. Dieser Ansatz hilft, die Fallstricke zu vermeiden, die mit unangemessener Vererbung verbunden sind.

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
}

Verwaltung verschiedener Vermietungen

Tim richtet eine Liste ein, um verschiedene Arten von Vermietungen zu verwalten. Dabei nutzt er die IRental-Schnittstelle, um verschiedene Miettypen zu speichern, darunter Lastwagen, Segelboote und Autos.

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" }
};

Dieses Design ermöglicht das Durchlaufen der Mietobjekte und den Zugriff auf gemeinsame Eigenschaften wie CurrentRenter, PricePerDay und 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}");
}

Casting auf bestimmte Typen

Um auf spezifische Eigenschaften und Methoden verschiedener Miettypen zuzugreifen, demonstriert Tim, wie man das Schlüsselwort is verwendet, um Objekte zu überprüfen und in ihre jeweiligen Typen zu überführen.

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
    }
}

Flexibilität mit Schnittstellen

Tim betont, dass die Verwendung von Schnittstellen Flexibilität für zukünftige Änderungen bietet. Das Hinzufügen neuer Miettypen, wie z. B. Panzer oder Fernsehgeräte, würde die bestehende Struktur nicht stören.

Übermäßige Verwendung von Vererbung vermeiden

Tim rät davon ab, Vererbung für die gemeinsame Nutzung von Code übermäßig zu nutzen, da dies zu einer unübersichtlichen und unflexiblen Codebasis führen kann. Stattdessen empfiehlt er die Nutzung von Schnittstellen und Komposition, um die gewünschten Ergebnisse zu erzielen, ohne die "is-a"-Beziehung über ihre logischen Grenzen hinaus zu dehnen.

Abschluss

Tim Coreys Erklärung von Vererbung und Schnittstellen in OOP bietet einen klaren Weg zur Erstellung von wartbarem und flexiblem Code. Indem er häufige Fallstricke aufzeigt und ein ausgefeiltes Design mit Schnittstellen bereitstellt, stellt er sicher, dass Entwickler fundierte Entscheidungen über die effektive Strukturierung ihrer Anwendungen treffen können.

Um einen tieferen Einblick in diese Konzepte zu erhalten und sie in Aktion zu sehen, sehen Sie sich Tims vollständiges Video an. Sein Kanal ist eine wahre Fundgrube für Programmier-Tutorials. Verpassen Sie es nicht!

Hero Worlddot related to Verstehen von C# OOP
Hero Affiliate related to Verstehen von C# OOP

Verdienen Sie mehr, indem Sie teilen, was Sie lieben

Erstellen Sie Inhalte für Entwickler, die mit .NET, C#, Java, Python oder Node.js arbeiten? Verwandeln Sie Ihr Fachwissen in ein zusätzliches Einkommen!

Iron Support Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an