Passer au contenu du pied de page
Iron Academy Logo
Apprendre le C#
Apprendre le C#

Autres catégories

Comprendre la POO C#

Tim Corey
49m 41s

L'héritage et les interfaces font partie intégrante de la programmation orientée objet (POO). Tim Corey, dans sa vidéo "Héritage vs Interfaces en C# : Object Oriented Programming, Tim Corey explique en détail quand utiliser l'héritage et quand opter pour les interfaces.

Cet article est un guide complet de la vidéo de Tim Corey. Elle décompose les concepts clés, les exemples et les explications de code fournis dans la vidéo, en soulignant les différences entre l'héritage et les interfaces et quand les utiliser.

Introduction

Tim at (0:00) commence par souligner l'importance de la distinction entre l'héritage et les interfaces. Il insiste sur la nécessité de comprendre quand utiliser chaque concept pour obtenir les meilleurs résultats. Son objectif est de le démontrer à l'aide d'exemples, en commençant par l'utilisation incorrecte de l'héritage unique, puis en la corrigeant.

Création du projet

À (1:08), Tim crée une application console simple à l'aide de .NET 5. Il nomme le projet "OODemoApp" et explique que l'objectif principal est de démontrer les concepts plutôt que de créer un code prêt à la production.

Comprendre l'héritage

Tim (1:55) se penche sur les principes de base de l'héritage. Il définit l'héritage comme un mécanisme par lequel les propriétés et les méthodes d'une classe de base sont héritées par une classe dérivée. Il insiste sur le fait que l'héritage ne doit pas être utilisé uniquement pour la réutilisation et le partage du code, mais pour établir une relation logique "is-a".

Points clés :

  • Is-a Relationship : garantit que la classe dérivée est un type de la classe de base.
  • <Logique commune : les classes héritées doivent partager une logique commune, et pas seulement des propriétés ou des signatures de méthodes.

Exemple de classe : Voiture de location

Tim (7:52) crée une classe RentalCar pour illustrer le concept fondamental de l'héritage. Cette classe représente une voiture de location dans une agence de location à Miami, en Floride.

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

Démonstration des pièges de l'héritage incorrect

Tim (10:15) explique comment une mauvaise utilisation de l'héritage peut entraîner des problèmes. Il souligne que si l'héritage est mal utilisé, il peut conduire à un code difficile à gérer et à étendre. Il déconseille d'utiliser l'héritage uniquement pour partager du code.

Ajouter des Enums pour les types de voitures et de camions

Tim (10:45) ajoute une énumération pour les types de voitures. Il crée un nouveau fichier de classe nommé Enums.cs pour conserver toutes les énumérations en un seul endroit. Cette énumération permettra de différencier les différents styles de voitures.

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

Il ajoute ensuite une propriété à la classe RentalCar pour spécifier le type de voiture.

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
}

Expansion pour inclure les camions

Comme l'explique Tim (12:27), l'agence de location décide d'ajouter des camions à sa flotte, ce qui entraîne de nouvelles exigences. Il crée une classe RentalTruck héritant de la classe mère 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
}

Il définit ensuite un nouvel enum pour les types de camions.

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

Gestion des différents types de propriétés

Tim (15:28) souligne que ce n'est pas parce que deux propriétés portent le même nom qu'elles sont identiques. Il illustre cela avec la propriété Style, qui pourrait signifier différents enums (CarType pour les voitures et TruckType pour les camions).

Introduction des bateaux dans la flotte

L'agence de location étend sa flotte aux bateaux. Tim montre comment procéder en créant une classe RentalBoat. Au départ, cela semble gérable puisque les bateaux peuvent partager certaines propriétés avec les voitures et les camions.

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

Traiter avec des voiliers

L'introduction des voiliers présente un défi car les voiliers n'ont pas de moteur. Tim (19:57) illustre les limites de l'héritage dans ce scénario.

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

Résoudre le problème avec les interfaces

Tim suggère de rendre les méthodes StartEngine et StopEngine virtuelles dans la classe de base afin de permettre leur remplacement dans les classes dérivées qui n'utilisent pas ces méthodes.

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

Gestion des méthodes qui ne doivent pas être appelées

Tim à (21:56) explique les pièges liés à l'utilisation de méthodes dans les classes héritées qui ne doivent pas être appelées. Pour l'exemple de la classe RentalSailboat, qui n'a pas de moteur, elle hérite des méthodes StartEngine et StopEngine de la classe RentalVehicle. Cette situation peut entraîner des problèmes si ces méthodes sont appelées par inadvertance, car elles doivent lancer des exceptions pour indiquer qu'elles ne sont pas applicables.

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

Reconnaître les limites de l'héritage

Tim (24:06) souligne comment l'héritage peut conduire à une base de code alambiquée et désordonnée lorsqu'il n'a plus de sens logique. Par exemple, un voilier ne doit pas être traité comme un RentalVehicle avec un moteur. Cela démontre les limites de l'héritage et la nécessité d'une meilleure approche de conception.

Mieux concevoir les interfaces

Pour résoudre ces problèmes, Tim propose une meilleure conception à l'aide d'interfaces. Il commence par créer un nouveau projet d'application console appelé "BetterOODemo" pour démontrer l'approche améliorée.

Définir l'interface

Tim présente l'interface IRental pour encapsuler les propriétés communes à toutes les locations.

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

Création de la classe de base pour les véhicules terrestres

Tim crée ensuite une classe de base pour les véhicules terrestres, séparant le concept de location de véhicules du véhicule lui-même.

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

En renommant la classe de base du véhicule en LandVehicle, Tim s'assure que seuls les véhicules appropriés héritent des méthodes liées au moteur.

Mise en œuvre des classes de voitures et de camions

Tim crée des classes Car et Truck qui héritent de LandVehicle et implémentent l'interface 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; }
}

Cette conception maintient une séparation claire des préoccupations, en veillant à ce que chaque classe ne possède que les propriétés et les méthodes correspondant à son type.

Éviter la duplication de code

Tim (31:41) souligne l'importance d'éviter la duplication inutile du code. Il explique que si l'interface IRental requiert les mêmes propriétés dans plusieurs classes, cela n'est pas considéré comme une violation du principe DRY (Don't Repeat Yourself), car aucune logique n'est dupliquée, seules les déclarations de propriétés le sont.

Mise en œuvre de la classe de voilier de location

Tim (35:09) explique comment gérer la classe RentalSailboat séparément en implémentant l'interface IRental, au lieu d'hériter de LandVehicle. Cette approche permet d'éviter les pièges liés à un héritage inapproprié.

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
}

Gestion des différentes locations

Tim établit une liste pour gérer différents types de locations, en utilisant l'interface IRental pour stocker différents types de locations, y compris des camions, des voiliers et des voitures.

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

Ce design permet de parcourir les locations et d'accéder aux propriétés communes comme CurrentRenter, PricePerDay, et 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}");
}

Cast vers des types spécifiques

Pour accéder à des propriétés et méthodes spécifiques de différents types de location, Tim montre comment utiliser le mot-clé is pour vérifier et convertir des objets dans leurs types respectifs.

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é grâce aux interfaces

Tim insiste sur le fait que l'utilisation d'interfaces offre une certaine flexibilité pour les changements futurs. Par exemple, l'ajout de nouveaux types de location, comme les réservoirs ou les téléviseurs, ne perturberait pas la structure existante.

Éviter l'utilisation excessive de l'héritage

Tim conseille de ne pas abuser de l'héritage pour le partage de code, car cela peut conduire à une base de code alambiquée et inflexible. Il recommande plutôt de tirer parti des interfaces et de la composition pour obtenir les résultats souhaités sans étirer la relation "is-a" au-delà de ses limites logiques.

Conclusion

L'explication de Tim Corey sur l'héritage et les interfaces dans la POO offre une voie claire vers la création de codes faciles à maintenir et flexibles. En présentant les pièges les plus courants et en fournissant une conception raffinée des interfaces, il s'assure que les développeurs peuvent prendre des décisions éclairées pour structurer leurs applications de manière efficace.

Pour approfondir ces concepts et les voir à l'œuvre, regardez la vidéo complète de Tim. Son canal est une mine d'or de tutoriels de programmation. Ne manquez pas cette occasion !

Hero Worlddot related to Comprendre la POO C#
Hero Affiliate related to Comprendre la POO C#

Gagnez plus en partageant ce que vous aimez

Vous créez du contenu pour les développeurs travaillant avec .NET, C#, Java, Python ou Node.js ? Transformez votre expertise en revenu supplémentaire !

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi