里氏替换原则

120 阅读2分钟

里氏替换原则

里氏替换原则(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类拆分为两个接口:BirdFlyingBird。所有鸟类都可以是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编译错误,正确地避免了运行时错误