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');
}
}
}
将评论组合成一个答案...
我同意对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。所以