问题描述
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对象,这似乎违反了该原则。那么此代码是否违反它? 我需要一个很好的解释,因为我似乎听不懂。
解决方法
基本上,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.");
}
}
在此处声明ReadableList
和WritableList
接口的地方,可以使用接口隔离原理(ISP)避免冲突。
传达不支持添加项目的另一种方式可能是添加canAddItem(item): boolean
方法。该设计可能不那么优雅,但可以清楚地表明并非所有实现都支持该操作。
我实际上更喜欢LSP的定义:"LSP says that every subclass must obey the same contracts as the superclass"。 不仅可以用代码(最好是在IMO中)定义“合同”,还可以通过文档等来定义。
,扩展类或接口时,新类仍然是其扩展的类型。对此(IMO)进行推理的最简单方法是将子类视为超类的一种特殊类型。因此,它仍然是超类的实例,具有一些其他行为。
例如,您的VehicleWithDoor
仍然是Vehicle
,但它也有门。 Scooter
也是车辆,但没有门。如果您有打开车门的方法,则车辆必须有车门(因此,将踏板车传递给它时,编译时会出错)。对于采用某个特定类的对象的方法来说,这也是相同的,您可以传递作为其子类实例的对象,该方法仍然可以使用。
在实现方面,您可以安全地将任何对象强制转换为其超类型之一(例如Car和Scooter
到Vehicle
,Car
到VehicleWithDoors
),但不能反之亦然(如果您进行一些检查并明确地进行转换,则可以放心地这样做。)