当前位置: 首页 > 知识库问答 >
问题:

通过合同设计(DBC)违反了利斯科夫替代原则(LSP)?

锺离卓
2023-03-14
abstract class AbstractVehicle {}

abstract class AbstractFuelledVehicle extends AbstractVehicle
{
    private $lastRefuelPrice;

    final public function refuelVehicle(FuelInterface $fuel)
    {
        $this->checkFuelType($fuel);
        $this->lastRefuelPrice = $fuel->getCostPerLitre;
    }

    abstract protected function checkFuelType(FuelInterface $fuel);
}

abstract class AbstractNonFuelledVehicle extends AbstractVehicle { /* ... */ }

现在,让我们来看看“燃料”类:

abstract class AbstractFuel implements FuelInterface
{
    private $costPerLitre;

    final public function __construct($costPerLitre)
    {
        $this->costPerLitre = $costPerLitre;
    }

    final public function getCostPerLitre()
    {
        return $this->costPerLitre;
    }
}

interface FuelInterface
{
    public function getCostPerLitre();
}

以上是完成的所有抽象类,现在让我们看看具体的实现。首先,fuel的两个具体实现,包括一些贫血接口,以便我们可以正确地键入-提示/嗅探它们:

interface MotorVehicleFuelInterface {}

interface AviationFuelInterface {}

final class UnleadedPetrol extends AbstractFuel implements MotorVehicleFuelInterface {}

final class AvGas extends AbstractFuel implements AviationFuelInterface {}

最后,我们有了车辆的具体实现,它确保使用正确的燃料类型(接口)为特定的车辆类别加油,如果不兼容则抛出异常:

class Car extends AbstractFuelledVehicle
{
    final protected function checkFuelType(FuelInterface $fuel)
    {
        if(!($fuel instanceof MotorVehicleFuelInterface))
        {
            throw new Exception('You can only refuel a car with motor vehicle fuel');
        }
    }
}

class Jet extends AbstractFuelledVehicle
{
    final protected function checkFuelType(FuelInterface $fuel)
    {
        if(!($fuel instanceof AviationFuelInterface))
        {
            throw new Exception('You can only refuel a jet with aviation fuel');
        }
    }
}
    null

共有1个答案

赫连晋
2023-03-14

将评论组合成一个答案...

我同意对LSP的分析:最初的版本是一种违反,我们总是可以通过削弱等级制度顶端的契约来解决LSP违反。然而,我不认为这是一个优雅的解决方案。类型检查总是一种代码味道(在OOP中)。用OP自己的话来说,“...包括一些贫血的界面,以便我们可以键入-提示/嗅闻它们...”这里被嗅出的是糟糕设计的恶臭。

我的观点是LSP是这里最不受关注的;instanceof是一种OO代码气味。在这里,遵守LSP就像在腐烂的房子上涂上新漆:它可能看起来很漂亮,但基础从根本上来说仍然不牢固。从设计中消除类型检查。只有担心LSP。

 类似资料:
  • 我认为是这样的,因为子类在被替换时的行为不像基类,而且它抛出了一个RuntimeException? 我不完全确定,我想知道我的假设是否正确

  • 我正在详细学习LSP,我确实理解为什么强化先决条件违反了这一原则(使用来自http://www.ckode.dk/programming/solid-principles-part-3-liskovs-substitution-principle/#contravariance): 在这里,我清楚地看到,对于基类有效的东西对于它的派生类将失败。换句话说,在不改变行为的情况下,我无法用基类的导数替换

  • 以下代码是否直接违反了Liskov替换原则: 子类不应破坏父类的类型定义。 结果如下: 致命错误:b::baz(Foo $foo)的声明必须与a::baz(Baz $baz)兼容

  • 子类型的实际后置条件是通过组合(使用逻辑)基类型的后置条件和子类型的后置条件来创建的,这使得得到的后置条件更具限制性 以下是加强前置条件和削弱后置条件的例子,结果违反了LSP(链接): > 假设基类使用成员int。现在您的子类型要求int为正。这是强化的前提条件,现在任何以前用负整数工作得很好的代码都被破坏了。 示例: 基类postcondition保证方法的返回值在范围内,但随后子类型将post

  • 有一个很好的例子,在圆-椭圆问题中违反了Liskov替换原理。 下面是一个流行的SO答案的措辞(尽管是矩形和正方形): 在数学中,是一个。实际上,它是矩形的特化。“is a”使您希望使用继承来对此进行建模。但是,如果在代码中使派生自,那么应该可以在任何需要的地方使用。这导致了一些奇怪的行为。 想象一下,您的矩形基类上有和方法;这似乎完全符合逻辑。但是,如果矩形引用指向,则和没有意义,因为设置一个将

  • 这是在一次采访中问我的。 我回答他说,对于相同的输入集,父母和孩子都应该产生相同的输出集。如果子节点想要扩展父节点的功能,它应该只在父节点支持范围之外的新输入上执行。这样,孩子将维持其父母签订的合同。 我给他举了一个例子,一个api可能正在使用这样的父级 如果这个孩子在这里产生了不同的输出,那么这个孩子就违反了它的父母签订的合同。 他对我的回答不满意,并告诉我这是简单的压倒一切,不违反LSP。所以