Pouvez-vous expliquer le principe de substitution de Liskov avec un bon exemple de C#?

pouvez-vous expliquer le principe de substitution de Liskov (le " L " de solide) avec un bon exemple de C# couvrant tous les aspects du principe d'une manière simplifiée? Si c'est vraiment possible.

77
demandé sur Smi 2010-12-13 15:18:44

3 réponses

(cette réponse a été réécrite 2013-05-13, lire la discussion au bas des commentaires)

LSP est sur le point de suivre le contrat de la classe de base.

vous pouvez par exemple ne pas jeter de nouvelles exceptions dans les sous-classes car celui qui utilise la classe de base ne s'y attendrait pas. Il en va de même si la classe de base lance ArgumentNullException si un argument est manquant et que la sous-classe permet à l'argument d'être null, aussi une violation de la LSP.

voici un exemple de structure de classe qui viole LSP:

public interface IDuck
{
   void Swim();
   // contract says that IsSwimming should be true if Swim has been called.
   bool IsSwimming { get; }
}
public class OrganicDuck : IDuck
{
   public void Swim()
   {
      //do something to swim
   }

   bool IsSwimming { get { /* return if the duck is swimming */ } }
}
public class ElectricDuck : IDuck
{
   bool _isSwimming;

   public void Swim()
   {
      if (!IsTurnedOn)
        return;

      _isSwimming = true;
      //swim logic  

   }

   bool IsSwimming { get { return _isSwimming; } }
}

et le code d'appel

void MakeDuckSwim(IDuck duck)
{
    duck.Swim();
}

Comme vous pouvez le voir, il y a deux exemples de canards. Un canard bio et un canard électrique. L'électrique canard peut nager seulement si il est allumé. Cela brise le principe LSP car il doit être activé pour pouvoir nager comme le IsSwimming (qui fait également partie de la contrat) ne sera pas défini comme dans la classe de base.

vous pouvez bien sûr le résoudre en faisant quelque chose comme ça

void MakeDuckSwim(IDuck duck)
{
    if (duck is ElectricDuck)
        ((ElectricDuck)duck).TurnOn();
    duck.Swim();
}

mais cela briserait le principe ouvert/fermé et doit être implémenté partout (et donc génère encore du code unstable).

la solution appropriée serait d'allumer automatiquement le canard dans la méthode Swim et en faisant ainsi le canard électrique se comportent exactement comme défini par le IDuck interface

mise à Jour

Quelqu'un a ajouté un commentaire et l'a supprimé. Il avait un point valable que je voudrais aborder:

la solution avec tourner sur le canard à l'intérieur de la méthode Swim peut avoir des effets secondaires en travaillant avec la mise en œuvre réelle ( ElectricDuck ). Mais cela peut être résolu en utilisant un explicite interface implementation . à mon humble avis c'est plus probable que vous obtenez des problèmes en ne l'allumant pas dans Swim car il est prévu qu'il nagera en utilisant le IDuck interface

Update 2

a reformulé certaines parties pour les rendre plus claires.

117
répondu jgauffin 2015-01-21 21:13:41

LSP une Approche Pratique

partout où je regarde les exemples de C# de LSP, les gens ont utilisé des classes et des interfaces imaginaires. Voici la mise en œuvre pratique de LSP que j'ai implémentée dans l'un de nos systèmes.

scénario: supposons que nous ayons trois bases de données (clients hypothécaires, clients des comptes courants et clients des comptes D'épargne) qui fournissent des données sur les clients et nous avons besoin de données sur les clients pour le nom de famille de chaque client. Maintenant nous pouvons obtenir plus plus de 1 détail client de ces 3 bases de données contre un nom de famille donné.

mise en œuvre:

BUSINESS MODEL LAYER:

public class Customer
{
    // customer detail properties...
}

COUCHE D'ACCÈS AUX DONNÉES:

public interface IDataAccess
{
    Customer GetDetails(string lastName);
}

interface ci-dessus est implémentée par la classe abstraite

public abstract class BaseDataAccess : IDataAccess
{
    /// <summary> Enterprise library data block Database object. </summary>
    public Database Database;


    public Customer GetDetails(string lastName)
    {
        // use the database object to call the stored procedure to retrieve the customer details
    }
}

cette classe abstraite a une méthode commune "GetDetails" pour les 3 bases de données qui est étendu par chacune des classes de la base de données comme indiqué ci-dessous

ACCÈS AUX DONNÉES DES CLIENTS HYPOTHÉCAIRES:

public class MortgageCustomerDataAccess : BaseDataAccess
{
    public MortgageCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetMortgageCustomerDatabase();
    }
}

ACCÈS AUX DONNÉES DES CLIENTS DES COMPTES COURANTS:

public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
    public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetCurrentAccountCustomerDatabase();
    }
}

COMPTE D'ÉPARGNE ACCÈS AUX DONNÉES DES CLIENTS:

public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
    public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetSavingsAccountCustomerDatabase();
    }
}

une fois ces 3 classes d'accès aux données définies, nous attirons maintenant notre attention sur le client. Dans la couche D'affaires, nous avons CustomerServiceManager classe qui renvoie les détails du client à ses clients.

BUSINESS LAYER:

public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager
{
   public IEnumerable<Customer> GetCustomerDetails(string lastName)
   {
        IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>()
        {
            new MortgageCustomerDataAccess(new DatabaseFactory()), 
            new CurrentAccountCustomerDataAccess(new DatabaseFactory()),
            new SavingsAccountCustomerDataAccess(new DatabaseFactory())
        };

        IList<Customer> customers = new List<Customer>();

       foreach (IDataAccess nextDataAccess in dataAccess)
       {
            Customer customerDetail = nextDataAccess.GetDetails(lastName);
            customers.Add(customerDetail);
       }

        return customers;
   }
}

Je n'ai pas montré l'injection de dépendance pour le garder simple comme il se complique déjà maintenant.

maintenant, si nous avons une nouvelle base de données client, nous pouvons juste ajouter une nouvelle classe qui étend BaseDataAccess et fournit son objet de base de données.

bien sûr, nous avons besoin de procédures stockées identiques dans toutes les bases de données participantes.

enfin, le client de la classe CustomerServiceManager n'appellera que la méthode GetCustomerDetails, passera le nom lastName et ne se souciera pas de la provenance des données.

espérons que cela vous donnera une approche pratique pour comprendre LSP.

6
répondu Yawar Murtaza 2018-02-25 12:10:27

voici le code pour appliquer le principe de Liskov.

public abstract class Fruit
{
    public abstract string GetColor();
}

public class Orange : Fruit
{
    public override string GetColor()
    {
        return "Orange Color";
    }
}

public class Apple : Fruit
{
    public override string GetColor()
    {
        return "Red color";
    }
}

class Program
{
    static void Main(string[] args)
    {
        Fruit fruit = new Orange();

        Console.WriteLine(fruit.GetColor());

        fruit = new Apple();

        Console.WriteLine(fruit.GetColor());
    }
}

: "Les classes dérivées doivent être substituables à leurs classes de base (ou interfaces)" & "Les méthodes qui utilisent des références à des classes de base (ou à des interfaces) doivent pouvoir utiliser des méthodes des classes dérivées sans en connaître les détails."

-1
répondu mark333...333...333 2018-04-11 06:24:16