试图理解 Liskov 替换原则
Trying to understand Liskov substitution principle
我正在尝试了解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
}
}
我不确定这是否违反了它。该原则说,如果你有一个 class S 的对象,那么你可以用另一个 class T 的对象替换它,其中 S 是 T 的子 class。但是,如果我写了
Vehicle vehicle = new Vehicle();
function(vehicle);
这当然会产生编译错误,因为 Vehicle class 没有 openDoor() 方法。但这意味着我不能用它们的父对象 class,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.");
}
}
接口隔离原则 (ISP) 可用于避免此处的违规行为,您可以在此处声明 ReadableList
和 WritableList
接口。
表达可能不支持添加项目的另一种方式是添加 canAddItem(item): boolean
方法。设计可能不那么优雅,但很明显并非所有实现都支持该操作。
我实际上更喜欢 LSP 的这个定义:"LSP says that every subclass must obey the same contracts as the superclass"。
“合同”不仅可以在代码中定义(在 IMO 中更好),还可以通过文档等方式定义。
当您扩展 class 或接口时,新的 class 仍然是它扩展的类型。对此进行推理的最简单方法(IMO)是将 subclass 视为 superclass 的特殊类型。所以它仍然是 superclass 的一个实例,具有一些额外的行为。
例如,您的 VehicleWithDoor
仍然是 Vehicle
,但它也有门。 Scooter
也是一种交通工具,但它没有门。如果您有打开车门的方法,则该车必须有门(因此当您将滑板车传递给它时会出现编译时错误)。同样的,对于一个接受某个class的对象的方法,你可以传递一个作为其子class实例的对象,该方法仍然有效。
在实现方面,您可以安全地将任何对象转换为其超类型之一(例如 Car 和 Scooter
转换为 Vehicle
,Car
转换为 VehicleWithDoors
),但反之则不然(如果你做了一些检查并明确地转换它,你就可以安全地这样做)。
我正在尝试了解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
}
}
我不确定这是否违反了它。该原则说,如果你有一个 class S 的对象,那么你可以用另一个 class T 的对象替换它,其中 S 是 T 的子 class。但是,如果我写了
Vehicle vehicle = new Vehicle();
function(vehicle);
这当然会产生编译错误,因为 Vehicle class 没有 openDoor() 方法。但这意味着我不能用它们的父对象 class,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.");
}
}
接口隔离原则 (ISP) 可用于避免此处的违规行为,您可以在此处声明 ReadableList
和 WritableList
接口。
表达可能不支持添加项目的另一种方式是添加 canAddItem(item): boolean
方法。设计可能不那么优雅,但很明显并非所有实现都支持该操作。
我实际上更喜欢 LSP 的这个定义:"LSP says that every subclass must obey the same contracts as the superclass"。 “合同”不仅可以在代码中定义(在 IMO 中更好),还可以通过文档等方式定义。
当您扩展 class 或接口时,新的 class 仍然是它扩展的类型。对此进行推理的最简单方法(IMO)是将 subclass 视为 superclass 的特殊类型。所以它仍然是 superclass 的一个实例,具有一些额外的行为。
例如,您的 VehicleWithDoor
仍然是 Vehicle
,但它也有门。 Scooter
也是一种交通工具,但它没有门。如果您有打开车门的方法,则该车必须有门(因此当您将滑板车传递给它时会出现编译时错误)。同样的,对于一个接受某个class的对象的方法,你可以传递一个作为其子class实例的对象,该方法仍然有效。
在实现方面,您可以安全地将任何对象转换为其超类型之一(例如 Car 和 Scooter
转换为 Vehicle
,Car
转换为 VehicleWithDoors
),但反之则不然(如果你做了一些检查并明确地转换它,你就可以安全地这样做)。