试图了解Liskov替代原理

问题描述

我试图理解 Liskov替换原理,并且我有以下代码

class Vehicle {
}

class VehicleWithDoors extends Vehicle {
    public void openDoor () {
        System.out.println("Doors opened.");
    }
}

class Car extends VehicleWithDoors {
}

class Scooter extends Vehicle {
}

class Liskov {
    public static void function(VehicleWithDoors vehicle) {
        vehicle.openDoor();
    }

    public static void main(String[] args) {
        Car car = new Car();
        function(car);
        Scooter scooter = new Scooter();
        //function(scooter);  --> compile error
    }
}

我不确定这是否违反。原理说,如果您有一个类S的对象,则可以用另一个类T的对象代替它,其中S是T的子类。但是,如果我写了

Vehicle vehicle = new Vehicle();
function(vehicle);

这当然会产生编译错误,因为Vehicle类没有openDoor()方法。但这意味着我无法用其父类Vehicle替换VehicleWithDoors对象,这似乎违反了该原则。那么此代码是否违反它? 我需要一个很好的解释,因为我似乎听不懂。

解决方法

你倒退了。原理指出"if S is a subtype of T,then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program"

基本上,VehicleWithDoors应该在Vehicle工作的地方工作。显然,这并不意味着Vehicule应该在VehiculeWithDoors工作的地方工作。但是,换句话说,您应该能够在不影响程序正确性的情况下用专业化代替一般化。

违反示例的可能是ImmutableList扩展了定义List操作的add,其中不可变实现会引发异常。

class List {
  constructor() {
    this._items = [];
  }
  
  add(item) {
    this._items.push(item);
  }
  
  itemAt(index) {
    return this._items[index];
  }
}

class ImmutableList extends List {
  constructor() {
    super();
  }
  
  add(item) {
    throw new Error("Can't add items to an immutable list.");
  }
}

在此处声明ReadableListWritableList接口的地方,可以使用接口隔离原理(ISP)避免冲突。

传达不支持添加项目的另一种方式可能是添加canAddItem(item): boolean方法。该设计可能不那么优雅,但可以清楚地表明并非所有实现都支持该操作。

我实际上更喜欢LSP的定义:"LSP says that every subclass must obey the same contracts as the superclass"。 不仅可以用代码(最好是在IMO中)定义“合同”,还可以通过文档等来定义。

,

扩展类或接口时,新类仍然是其扩展的类型。对此(IMO)进行推理的最简单方法是将子类视为超类的一种特殊类型。因此,它仍然是超类的实例,具有一些其他行为。

例如,您的VehicleWithDoor仍然是Vehicle,但它也有门。 Scooter也是车辆,但没有门。如果您有打开车门的方法,则车辆必须有车门(因此,将踏板车传递给它时,编译时会出错)。对于采用某个特定类的对象的方法来说,这也是相同的,您可以传递作为其子类实例的对象,该方法仍然可以使用。

在实现方面,您可以安全地将任何对象强制转换为其超类型之一(例如Car和ScooterVehicleCarVehicleWithDoors),但不能反之亦然(如果您进行一些检查并明确地进行转换,则可以放心地这样做。)