谁能提供使用车辆的利斯科夫替代原理(LSP)的示例? [英] Can anyone provide an example of the Liskov Substitution Principle (LSP) using Vehicles?

查看:80
本文介绍了谁能提供使用车辆的利斯科夫替代原理(LSP)的示例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

《里斯科夫替代原理》指出,子类型应该可以替代该类型(在不改变程序正确性的前提下).

The Liskov Substitution Principle states that a subtype should be substitutable for that type (without altering the correctness of the program).

  • 有人可以在车辆(汽车)领域提供此原则的示例吗?
  • 有人可以举例说明在车辆领域违反该原则的情况吗?

我已经阅读了关于方形/矩形的示例,但是我认为带有车辆的示例将使我对概念更加了解.

I've read about the square/rectangle example, but I think that an example with vehicles will give me a better understanding of the concept.

推荐答案

对我来说,此 rel ="no 1996年鲍勃叔叔的报价( Robert C Martin )最好地总结了LSP:

For me, this 1996 Quote from Uncle Bob (Robert C Martin) summarises the LSP best:

使用指向基类的指针或引用的函数必须能够在不知道的情况下使用派生类的对象.

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

最近,作为基于(通常是抽象的)基类/父类的子类的继承抽象的替代方法,我们还经常使用 interfaces 进行多态抽象. LSP对使用者和抽象的实现都具有影响:

In recent times, as an alternative to inheritance abstractions based on sub-classing from a (usually abstract) base/super class, we also often use interfaces for polymorphic abstraction. The LSP has implications to both the consumer, and implementation of the abstraction:

  • 任何使用类或接口抽象的代码都必须在定义的抽象之外不对该类进行任何假设;
  • 任何超类的子类或抽象的实现都必须遵守抽象接口的要求和约定.

LSP符合性

这是一个使用接口IVehicle的示例,该接口可以具有多种实现方式(或者,您可以用多个子类将接口替换为抽象基类-效果相同).

Here is an example using an interface IVehicle which can have multiple implementations (alternatively, you can substitute the interface for an abstract base class with several subclasses - same effect).

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

IVehicle的使用者的此实现位于LSP的范围之内:

This implementation of a consumer of IVehicle stays within the bounds of LSP:

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

违规-运行时类型切换

这是一个违反LSP的示例,先使用RTTI,然后进行向下转换-鲍勃叔叔称这为明显违反":

Here's an example of a violation of LSP, using RTTI and then Downcasting - Uncle Bob calls this a 'glaring violation':

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

违法方法超出了约定的IVehicle接口,并破解了该接口(或子类,如果使用继承而不是接口)的已知实现的特定路径.鲍伯叔叔还解释说,使用类型转换行为的LSP违规通常还会违反

The violating method goes beyond the contracted IVehicle interface and hacks a specific path for a known implementation of the interface (or a subclass, if using inheritance instead of interfaces). Uncle Bob also explains that LSP violations using type-switching behaviour usually also violate the Open and Closed principle as well, since continual modification to the function will be required in order to accomodate new subclasses.

违反-前提条件通过子类型得到加强

另一个违反示例是通过子类型加强前提条件" :

public abstract 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);
     }
 }

在这里,Scooter子类试图违反LSP,因为它试图加强(进一步约束)基于基类Drive的前提条件miles < 300,现在最大距离小于50英里.这是无效的,因为根据Vehicle的合同定义,允许300英里.

Here, the Scooter subclass attempts to Violate the LSP as it tries to strengthen (further constrain) the precondition on the base class Drive method that miles < 300, to now a maximum of less than 50 miles. This is invalid, since by the contract definition of Vehicle allows 300 miles.

类似地,后置条件可能不会被子类型削弱(即放松).

Similarly, Post Conditions may not be weakened (i.e. relaxed) by a subtype.

(的用户C#中的代码合同将注意,前提条件和后置条件必须通过

(Users of Code Contracts in C# will note that preconditions and postconditions MUST be placed on the interface via a ContractClassFor class, and cannot be placed within implementation classes, thus avoiding the violation)

细微违规-子类滥用接口实现

可以通过实现该接口的可疑派生类来显示more subtle违规行为(也是Bob叔叔的术语):

A more subtle violation (also Uncle Bob's terminology) can be shown with a dubious derived class which implements the 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;}}
}

在这里,无论ToyCar被驱动了多远,剩余燃料始终为零,这对于IVehicle界面的用户来说是令人惊讶的(即MPG消耗无穷大-永续运动吗?).在这种情况下,问题在于,尽管ToyCar已经实现了接口的所有要求,但ToyCar本质上并不是真正的IVehicle,而只是橡皮图章"接口.

Here, irrespective of how far the ToyCar is driven, the fuel remaining will always be zero, which will be surprising to users of the IVehicle interface (i.e. infinite MPG consumption - perpetual motion?). In this case, the problem is that despite ToyCar having implemented all of the requirements of the interface, ToyCar just inherently isn't a real IVehicle and just "rubber stamps" the interface.

防止这种接口或抽象基类被滥用的一种方法是,确保在接口/抽象基类上提供一组良好的单元测试,以测试所有实现均符合期望(以及任何期望).假设).单元测试在记录典型用法方面也很出色.例如此NUnit Theory会拒绝将ToyCar纳入您的生产代码库:

One way to to prevent your interfaces or abstract base classes from being abused in this way is to ensure a good set of Unit Tests are made available on the interface / abstract base class to test that all implementations meet the expectations (and any assumptions). Unit tests are also great at documenting typical usage. e.g. this NUnit Theory will reject ToyCar from making it into your production code base:

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

编辑,回复:OpenDoor

打开门听起来完全像是一个不同的问题,因此需要相应地分开(例如,"S" "I" (在SOLID中)),例如

Opening doors sounds like a different concern entirely, so needs to be separated accordingly (i.e. the "S" and "I" in SOLID), e.g.

  • 在新接口IVehicleWithDoors上,该接口可以继承IVehicle
  • 或IMO最好在单独的接口IDoor上,然后CarTruck之类的车辆将同时实现IVehicleIDoor接口,但ScooterMotorcycle则不会.
  • 或什至3个接口,IVehicle(Drive()),IDoor(Open())和IVehicleWithDoors都继承了这两个接口.
  • on a new interface IVehicleWithDoors, which could inherit IVehicle
  • or IMO better still, on a separate interface IDoor, and then vehicles like Car and Truck would implement both IVehicle and IDoor interfaces, but Scooter and Motorcycle would not.
  • or even 3 interfaces, IVehicle (Drive()), IDoor (Open()) and IVehicleWithDoors which inherits both of these.

在所有情况下,为避免违反LSP,这些接口的必需对象的代码不应降低接口质量,以访问其他功能.该代码应选择所需的适当的最小接口/(超级)类,并仅坚持该接口上的约定功能.

In all cases, to avoid violating LSP, code which required objects of these interfaces should not downcast the interface to access extra functionality. The code should select the appropriate minimum interface / (super)class it needs, and stick to just the contracted functionality on that interface.

这篇关于谁能提供使用车辆的利斯科夫替代原理(LSP)的示例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆