里氏替换原则是在作继承设计时须要遵循的原则,不遵循了 LSP 的继承类会带来意想不到的问题。html
里氏替换原则(Liskov Substitution Principle) 是由 Barbara Liskov 在 1987 年提出来的,Liskov 是她的姓,国内翻译成 里氏。java
原则声明:若是类型 S 是类型 T 的子类型,那么 T 类型的对象能够替换成 S 类型的对象,而不会影响程序的行为。ide
LSP 对语言增长了新的签名约束(协变与逆变能够看这篇文章Java中的逆变与协变):ui
从契约角度来看,里氏替换原则有4层含义:this
继承描述的是 is-a
关系,开闭原则要求咱们使用继承增长功能,LSP 原则是指导咱们如何继承。编码
在之前写的一篇里氏替换原则 的文章里,我提到过:.net
每一个类都会有public方法,有些类会实现interface,供其余类使用,自身就处在一个服务的位置上。 每一个public方法都是自身所作出的一个承诺,只要你按照要求调用,就会提供正确的服务。 子类在继承后,当然是得到了超类的带来的‘财富’,更重要的是要遵照超类作出的承诺, 破坏了这个承诺其实是没有资格继承超类的。
若是破坏了继承原则,那么开闭原则也就没法使用。子类不按照契约设定编码,那就是在给使用者挖坑。翻译
需求要求设计一个鸟的继承体系,以下是咱们设计的抽象基类:设计
public abstract class Bird { private String name; public void setName(String name){ this.name = name; } public void fly() { System.out.println(name + " fly"); } }
大部分鸟在这个基类中都工做的很好,可是有一天来了一只企鹅,企鹅是不会飞的,所以咱们重写 fly
方法code
public class Penguin { @Override public void fly() { throw new RuntimeException(); } }
因为企鹅不会飞,在 fly
方法里直接抛出了异常。
注意,这里已经违反了 LSP 原则,在基类中并无异常抛出,使用方正常使用,而在 Penguin
类中 fly
方法抛出了异常,违反了基类遵照的契约。
要解决这个问题,咱们须要应用接口分离原则来拆分 Bird
类,由 Penguin
来看, fly
功能并非 Bird
承担的职责,应该将其单独放到一个接口中,会飞的鸟自行实现。若是像上面那样,大部分鸟都有一个默认的飞行实现,则咱们能够作一个默认的飞行实现类,使用组合的方式放到会飞的鸟中。
public abstract class Bird { private String name; public void setName(String name){ this.name = name; } } public interface Flyable { public void fly(); }
里氏替换原则是继承须要遵循的原则,有时咱们可能在无心中就已经违反了原则要求,一是由于咱们没有意识到,二是咱们设计的接口、抽象基类有问题。遇到违反 LSP 原则的继承,有两招来解决:1. 修改实现,2。 更改设计。