里氏替换原则,主要是一个关于继承的规范原则,它要求我们在软件中写继承关系时,所有引用父类的地方必须能够
透明地使用其子类对象,子类可以实现父类所提供的抽象方法,但不要去重写父类已经实现的方法,或者重载父类的
构造。要维护继承的传递性,当然很多地方为这个原则打了个比喻为:“龙生龙,凤生凤,老鼠的儿子会打洞”
里氏替换原则,告诉我们,在软件中将一个父类对象替换成它的子类对象,程序将不会产生任何错误或异常的话,那
么这两个对象才具有继承关系,也就是IS-A关系。当然,反过来说,使用子类对象的地方却不一定都能使用其父类进
行替换,我相信这个应该很好理解,子类扩展的地方,父类就是没有办法实现的。
为了遵循里氏替换原则,我们在软件中通常有种写法,在程序中我们采用父类的基本类型来对对象进行定义,而在运
行时再确定其子类类型,目的就是用子类对象来替换父类对象,所以很多原则,实际上在我们平时写代码这个过程
中,就有了体现。
在使用里氏替换原则时,需要注意的事项有:
1、子类的所有方法必须要在父类中有所声明,或者换句话说,就是子类必须实现父类中声明的所有方法。为什
么要有这个事项呢?其实很好理解,我们在写程序时,经常用父类来声明对象定义,这样有助于保证系统的扩展性,
在运行时用子类对象来替换父类对象,那么如果一个方法只存在子类中,在父类中却不提供对应的声明,那么父类对
对象如何去使用这个方法呢?
2、我们在使用里氏替换原则时,尽量把父类设计成抽象类或接口,让子类继承或实现其方法,运行时,由子类
对象替换掉父类对象,这样提高我们程序的扩展性,如果需要扩展其他类,无须修改原有子类的代码,只需要其他子
类继承或实现对应的父类或父接口即可,里氏替换原则其实也是开闭原则的一种体现。
举个符合替换原则的例子:
父类中,我们提供了2个方法,一个eat(),例外一个是breathe(),breathe()是一个抽象方法,因为这是子类特有的扩
展方法,父类无须实现, 但需要声明。eat()在父类中已经有过实现,那么子类只需继承即可,而无须重写,确保继承
的连贯性,老鼠的儿子不能连洞都不会打了,改去追狗了……。
代码体现:
package yuanze;
/**
* 将父类声明为抽象类
*
* @author Administrator
*
*/
public abstract class Super {
/**
* 定义一个父类无须实现的抽象方法
*/
public abstract void breathe();
/**
* 定义父类需要实现的方法
*/
public void eat() {
System.out.println("我吃面");
}
}
子类:
package yuanze;
/**
* 定义子类继承父类
*
* @author Administrator
*
*/
public class Child extends Super {
/**
* 子类扩展的方法,但是在父类中有声明
*/
@Override
public void breathe() {
// TODO Auto-generated method stub
System.out.println("呼吸");
}
}
客户端调用时:
package yuanze;
public class Test {
public static void main(String[] args) {
Super c = new Child(); // 由子类的实例替代父类的实例
c.eat();
}
}
执行后的结果是:
我吃面
也就是说:子类继承父类后,无论继承多少次,那么只要eat(),那么就一定得到的是“我吃面”。如果子类重写了父类
的eat()方法,那么就可能出现“我跟着老婆走,我吃大米了!”的情况了,那就违背了里氏替换原则。可以这样理解,
父类中凡是已经实现好方法,那就是需要你继承着走的,那都是设计的一系列的规范和契约,如果任意去修改,就有
可能对整个继承体系造成破坏。子类需要扩展的方法,一定要通知到自己的父类,也就是需要在父类中声明。
通俗的说,这个原则就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法(不能把Y基因变成了Z,O(∩_∩)O哈哈~)。
2、子类中可以增加自己特有的方法,但需要在父类中声明(要跟到老婆走,是不先要给父亲说些呢?)。
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,这个
从参数个数上考虑。
4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更加严格,如父类要
求返回List,那么子类就应该返回List的实现ArrayList,父类是采用泛型,那么子类则不能采用泛型,而是具体的返
回。