Quelqu'un peut-il donner un exemple du principe de substitution de Liskov (LSP) en utilisant des véhicules?

le principe de substitution de Liskov stipule qu'un sous-type doit être substituable à ce type (sans modifier l'exactitude du programme).

  • quelqu'un Peut-il donner un exemple de ce principe dans le domaine des véhicules (automobile)?
  • Quelqu'un peut-il fournir un exemple de violation de ce principe dans le domaine des véhicules?

j'ai lu à propos de l'exemple carré/rectangle, mais je pense qu'un exemple avec des véhicules me donnera une meilleure compréhension du concept.

28
demandé sur StuartLC 2013-12-31 21:33:13

5 réponses

Pour moi, cette 1996 Citation à partir de Oncle Bob ( Robert C Martin ) résume les LSP:

les fonctions qui utilisent des pointeurs ou des références à des classes de base doivent pouvoir utiliser des objets de classes dérivées sans le savoir.

dans les temps récents, comme une alternative aux abstractions d'héritage basées sur la sous-classification de a (Habituellement abstract) base/super class, nous utilisons aussi souvent interfaces pour l'abstraction polymorphe. La PSL a des implications à la fois pour le consommateur, et la mise en œuvre de l'abstraction:

  • tout code consommant une abstraction de classe ou d'interface ne doit rien supposer d'autre au sujet de la classe au-delà de l'abstraction définie;
  • toute subdivision d'une superclasse ou mise en œuvre d'une abstraction doit respecter les exigences et les conventions de l'interface à l'abstraction.

LSP de Conformité

voici un exemple utilisant une interface IVehicle qui peut avoir plusieurs implémentations (alternativement, vous pouvez substituer l'interface à une classe de base abstraite avec plusieurs sous - classes-même effet).

interface IVehicle
{
   void Drive(int miles);
   void FillUpWithFuel();
   int FuelRemaining {get; } // C# syntax for a readable property
}

Cette mise en œuvre d'un consommateur de IVehicle reste dans les limites de LSP:

void MethodWhichUsesIVehicle(IVehicle aVehicle)
{
   ...
   // Knows only about the interface. Any IVehicle is supported
   aVehicle.Drive(50);
 }

violation flagrante-changement de type D'exécution

voici un exemple de violation de la LSP, en utilisant RTTI puis Downcasting - oncle Bob appelle cela une "violation flagrante":

void MethodWhichViolatesLSP(IVehicle aVehicle)
{
   if (aVehicle is Car)
   {
      var car = aVehicle as Car;
      // Do something special for car - this method is not on the IVehicle interface
      car.ChangeGear();
    }
    // etc.
 }

la méthode de violation va au-delà de l'interface contractée IVehicle et pirate un chemin spécifique pour une implémentation connue de l'interface (ou d'une sous-classe, si vous utilisez l'héritage à la place des interfaces). L'oncle Bob explique également que les violations de la LSP en utilisant un comportement de changement de type violent généralement aussi le Principe ouvert et fermé ainsi, puisque la modification continue de la fonction sera nécessaire afin de s'adapter à de nouvelles sous-classes.

Violation-la condition préalable est renforcée par un sous-type

un autre exemple d'infraction serait lorsqu'une condition préalable est renforcée par un sous-type" :

public class Vehicle
{
    public virtual void Drive(int miles)
    {
        Assert(miles > 0 && miles < 300); // Consumers see this as the contract
    }
 }

 public class Scooter : Vehicle
 {
     public override void Drive(int miles)
     {
         Assert(miles > 0 && miles < 50); // ** Violation
         base.Drive(miles);
     }
 }

ici , la sous-classe Scooter tente de violer la LSP alors qu'elle tente de renforcer (davantage de contrainte) la condition préalable sur la classe de base Drive méthode que miles < 300 , à présent un maximum de moins de 50 miles. Ceci n'est pas valable, puisque selon la définition du contrat de IVehicle permet 300 miles.

de même, les conditions postérieures ne peuvent pas être affaiblies (c'est-à-dire assouplies) par un sous-type.

(les utilisateurs de Code Contracts en C# noteront que les conditions préalables et postconditions doivent être placées sur interface via une classe ContractClassFor , et ne peuvent pas être placés dans des classes de mise en œuvre, évitant ainsi la violation)

Subtil de Violation de l'Abus d'une interface de mise en œuvre de la sous-classe

A more subtle violation (aussi terminologie D'oncle Bob) peut être montré avec une classe dérivée douteuse qui met en œuvre l'interface:

class ToyCar : IVehicle
{
    public void Drive(int miles) { /* Show flashy lights, make random sounds */ }
    public void FillUpWithFuel() {/* Again, more silly lights and noises*/}
    public int FuelRemaining {get {return 0;}}
}

ici, quelle que soit la distance parcourue par le ToyCar , le carburant restant sera toujours nul, ce qui surprendra les utilisateurs de l'interface IVehicle (consommation infinie MPG - mouvement perpétuel).). Dans ce cas, le problème est que malgré ToyCar ayant mis en œuvre toutes les exigences de la interface, ToyCar n'est pas intrinsèquement un vrai IVehicle et juste" tampons en caoutchouc " l'interface.

une façon d'empêcher vos interfaces ou classes de base abstraites d'être abusées de cette manière est de s'assurer qu'un bon ensemble de Tests unitaires sont disponibles sur la classe interface / base abstraite pour tester que toutes les implémentations répondent aux attentes (et aux hypothèses). Les tests unitaires sont également très utiles pour documenter l'utilisation typique. par exemple ce NUnit Theory rejeter ToyCar de sa fabrication dans votre base de code de production:

[Theory]
void EnsureThatIVehicleConsumesFuelWhenDriven(IVehicle vehicle)
{
    vehicle.FillUpWithFuel();
    Assert.IsTrue(vehicle.FuelRemaining > 0);
    int fuelBeforeDrive = vehicle.FuelRemaining;
    vehicle.Drive(20); // Fuel consumption is expected.
    Assert.IsTrue(vehicle.FuelRemaining < fuelBeforeDrive);
}

Edit, Re: OpenDoor

ouvrir les portes sonne comme une préoccupation entièrement différente, il doit donc être séparé en conséquence (i.e. le S " et I" in SOLID), e.g.

  • sur une nouvelle interface IVehicleWithDoors , qui pourrait hériter IVehicle
  • ou IMO mieux encore, sur une interface séparée IDoor , et puis les véhicules comme Car et Truck serait mettre en œuvre à la fois IVehicle et IDoor interfaces, mais Scooter et Motorcycle ne serait pas.
  • ou même 3 interfaces, IVehicle ( Drive() ), IDoor ( Open() ) et IVehicleWithDoors qui hérite de ces deux.

In dans tous les cas, pour éviter de violer la LSP, le code qui nécessitait des objets de ces interfaces ne devrait pas dévaloriser l'interface pour accéder à des fonctionnalités supplémentaires. Le code doit sélectionner la classe minimum d'interface / (super)appropriée dont il a besoin, et s'en tenir à la seule fonctionnalité contractée sur cette interface.

45
répondu StuartLC 2018-03-21 07:25:24

Image que je veux louer une voiture quand je suis en cours de déménagement. J'appelle la société de location et je leur demande quels modèles ils ont. On me dit cependant que je vais juste recevoir la prochaine voiture qui vient disponible:

public class CarHireService {
    public Car hireCar() {
        return availableCarPool.getNextCar();
    }
}

mais ils m'ont donné une brochure qui me dit que tous leurs modèles viennent avec ces caractéristiques:

public interface Car {
    public void drive();
    public void playRadio();
    public void addLuggage();
}

cela sonne juste ce que je cherche, donc je réserve une voiture et je m'en vais heureux. Le jour du déménagement, une voiture de Formule 1 apparaît devant ma maison:

public class FormulaOneCar implements Car {
    public void drive() {
        //Code to make it go super fast
    }

    public void addLuggage() {
        throw new NotSupportedException("No room to carry luggage, sorry."); 
    }

    public void playRadio() {
        throw new NotSupportedException("Too heavy, none included."); 
    }
}

Je ne suis pas heureux, parce que j'ai été essentiellement menti par leur brochure - il n'a pas d'importance si la voiture de Formule 1 a une fausse botte que ressemble à il peut contenir des bagages mais ne veut pas ouvrir, c'est inutile pour déplacer la maison!

si on me dit que "ce sont les choses que toutes nos voitures font", alors n'importe quelle voiture que je reçois devrait se comporter de cette façon. Si Je ne peux pas faire confiance aux détails dans leur brochure, c'est inutile. C'est l'essence du principe de Substitution Liskov .

17
répondu anotherdave 2014-01-09 09:38:45

le principe de substitution de Liskov stipule qu'un objet avec une certaine interface peut être remplacé par un autre objet qui implémente la même interface tout en conservant toute la justesse du programme original. Cela signifie que non seulement l'interface d'avoir exactement les mêmes types, mais le comportement doit rester correct.

dans un véhicule, vous devriez être en mesure de remplacer une pièce par une autre pièce, et la voiture continuerait à fonctionner. Disons que votre ancienne radio n'a pas de SYNTONISEUR NUMÉRIQUE, mais vous voulez écouter la radio HD pour acheter une nouvelle radio qui a un récepteur HD. Vous devriez pouvoir débrancher l'ancienne radio et brancher la nouvelle, à condition qu'elle ait la même interface. Sur la surface, cela signifie que la prise électrique qui relie la radio de la voiture doit être la même forme sur la nouvelle radio c'est sur la vieille radio. Si la fiche de la voiture est rectangulaire et a 15 pins, alors la prise de la nouvelle radio doit être rectangulaire et ont 15 broches.

mais il y a d'autres considérations en plus de l'ajustement mécanique: le comportement électrique sur la fiche doit être le même, aussi. Si la broche 1 sur le connecteur de l'ancienne radio est +12V, alors la broche 1 sur le connecteur de la nouvelle radio doit aussi être +12V. Si la broche 1 de la nouvelle radio était la broche" haut-parleur gauche hors tension", la radio pourrait court-circuiter, ou souffler un fusible. Ce serait une violation évidente de la LSP.

vous pourriez prenons aussi une situation de déclassement: disons que votre radio chère meurt, et que vous ne pouvez vous permettre qu'une radio AM. Il n'a pas de stéréo, mais il a le même connecteur que votre radio. Disons que la broche 3 est hors tension, et la broche 4 est hors tension. Si votre radio AM joue le signal monophonique à la fois sur les pins 3 et 4, Vous pouvez dire que son comportement est cohérent, et ce serait une substitution acceptable. Mais si votre nouvelle radio AM ne joue de l'audio que sur pin 3, et rien sur la pin 4, le son serait déséquilibré, et ce ne serait probablement pas une substitution acceptable. Cette situation violerait également la LSP, parce que bien que vous pouvez entendre des sons, et pas de fusibles soufflent, la radio ne répond pas à la spécification complète de l'interface.

3
répondu John Deters 2014-01-03 04:35:58

tout d'abord, vous devez définir ce qu'est un véhicule et une automobile. Selon Google (définitions pas très complètes):



véhicule:

une chose utilisée pour le transport de personnes ou de marchandises, en particulier: sur terre, comme une voiture, un camion ou une charrette.



de l'Automobile:

un véhicule routier, généralement à quatre roues, propulsé par un moteur à combustion interne ou électrique

et capable de transporter un petit nombre de personnes



Si une automobile est un véhicule, mais un véhicule n'est pas une automobile.

2
répondu user2810910 2013-12-31 17:51:55

à mon avis, pour archiver le LSP, les sous-types ne peuvent jamais ajouter de nouvelles méthodes publiques. Juste des méthodes et des champs privés. Et bien sûr, les sous-types peuvent outrepasser les méthodes de la classe de base. Si un sous-type a une méthode publique unique que le type de base n'a pas, vous ne pouvez tout simplement pas remplacer le sous-type par le type de base. Si vous passez une instance à la méthode d'un client par laquelle vous recevez une instance d'un sous-type mais le type de paramètre est le type de base ou si vous avez une collection de type basetype où aussi les sous-types font partie de, Alors comment pouvez-vous jamais appeler la méthode de la classe des sous-types sans demander pour son type en utilisant un si statment et si le type correspond, faire un cast à ce sous-Type afin d'appeler la méthode sur elle.

-2
répondu brighty 2015-02-10 15:07:51