里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)
核心思想:所有引用基类的地方必须能透明地使用其子类的对象。
案例分析:
案例一:
以动物为例,陆地上的动物都能在地上跑,但猴子除了能在陆地上跑之外还会爬树。可以为猴单独定义一个类 Monkey。
abstract class Animal {
constructor(public name) {}
abstract moving(): void
}
class TerrestrialAnimal extends Animal {
moving() {
console.log('I am moving on the ground')
}
}
class AquaticAnimal extends Animal {
moving() {
console.log('I am moving in the water')
}
}
class AerialAnimal extends Animal {
moving() {
console.log('I am moving in the air')
}
}
class Monkey extends TerrestrialAnimal {
climbing() {
console.log('I am climbing the tree')
}
}
class Zoo {
constructor(public animals: Animal[] = []) {}
addAnimal(animal: Animal) {
this.animals.push(animal)
}
displayActivity() {
this.animals.forEach(animal => animal.moving())
}
monkeyClimbing(monkey: Monkey) {
monkey.climbing()
}
}
function test() {
const lion = new TerrestrialAnimal('lion')
const fish = new AquaticAnimal('fish')
const eagle = new AerialAnimal('eagle')
const monkey = new Monkey('monkey')
const zoo = new Zoo()
zoo.addAnimal(lion)
zoo.addAnimal(fish)
zoo.addAnimal(eagle)
zoo.addAnimal(monkey)
zoo.displayActivity()
zoo.monkeyClimbing(monkey)
}
test()
这里 Zoo 的 addAnimal 方法接受 Animal 类的对象,所以 Animal 子类的对象都能传入,符合里氏替换原则。但 Zoo 的 monkeyClimbing 方法只接受 Monkey 类的对象,当传入 TerrestrialAnimal(Monkey 的父类) 的对象时,程序将报错,这说明能出现子类的地方,父类不一定能出现。
案例二:
假设有一个Bird类,它有一个fly方法。然后,我们有一个Penguin类继承自Bird类。显然,企鹅是鸟类,但它们不能飞。如果我们让Penguin类继承Bird类,并重写fly方法以抛出错误或进行无操作,这将违反LSP,因为子类的行为改变了父类的预期行为。
class Bird {
fly(): void {
console.log("This bird is flying.");
}
}
class Penguin extends Bird {
fly(): void {
throw new Error("Cannot fly.");
}
}
function makeBirdFly(bird: Bird) {
bird.fly();
}
const bird = new Bird();
makeBirdFly(bird); // 正常工作
const penguin = new Penguin();
makeBirdFly(penguin); // 运行时错误:Cannot fly.
为了遵守LSP,可以将Bird类拆分为两个接口:Bird和FlyingBird。所有鸟类都可以是Bird,但只有会飞的鸟类才实现FlyingBird接口。这样,就不会期望所有鸟类都能飞,从而符合LSP。
interface Bird {
eat(): void;
}
interface FlyingBird extends Bird {
fly(): void;
}
class Sparrow implements FlyingBird {
eat(): void {
console.log("This sparrow is eating.");
}
fly(): void {
console.log("This sparrow is flying.");
}
}
class Penguin implements Bird {
eat(): void {
console.log("This penguin is eating.");
}
}
function makeBirdEat(bird: Bird) {
bird.eat();
}
function makeFlyingBirdFly(bird: FlyingBird) {
bird.fly();
}
const sparrow = new Sparrow();
makeBirdEat(sparrow); // 正常工作
makeFlyingBirdFly(sparrow); // 正常工作
const penguin = new Penguin();
makeBirdEat(penguin); // 正常工作
// makeFlyingBirdFly(penguin); // TypeScript编译错误,正确地避免了运行时错误